'use strict'; /** * @ngdoc object * @name ng.$routeProvider * @function * * @description * * Used for configuring routes. See {@link ng.$route $route} for an example. */ function $RouteProvider(){ var routes = {}; /** * @ngdoc method * @name ng.$routeProvider#when * @methodOf ng.$routeProvider * * @param {string} path Route path (matched against `$location.path`). If `$location.path` * contains redundant trailing slash or is missing one, the route will still match and the * `$location.path` will be updated to add or drop the trailing slash to exactly match the * route definition. * * `path` can contain named groups starting with a colon (`:name`). All characters up to the * next slash are matched and stored in `$routeParams` under the given `name` after the route * is resolved. * * @param {Object} route Mapping information to be assigned to `$route.current` on route * match. * * Object properties: * * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly * created scope or the name of a {@link angular.Module#controller registered controller} * if passed as a string. * - `template` – `{string=}` – html template as a string that should be used by * {@link ng.directive:ngView ngView} or * {@link ng.directive:ngInclude ngInclude} directives. * this property takes precedence over `templateUrl`. * - `templateUrl` – `{string=}` – path to an html template that should be used by * {@link ng.directive:ngView ngView}. * - `resolve` - `{Object.=}` - An optional map of dependencies which should * be injected into the controller. If any of these dependencies are promises, they will be * resolved and converted to a value before the controller is instantiated and the * `$routeChangeSuccess` event is fired. The map object is: * * - `key` – `{string}`: a name of a dependency to be injected into the controller. * - `factory` - `{string|function}`: If `string` then it is an alias for a service. * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} * and the return value is treated as the dependency. If the result is a promise, it is resolved * before its value is injected into the controller. Be aware that `ngRoute.$routeParams` will * still refer to the previous route within these resolve functions. Use `$route.current.params` * to access the new route parameters, instead. * * - `redirectTo` – {(string|function())=} – value to update * {@link ng.$location $location} path with and trigger route redirection. * * If `redirectTo` is a function, it will be called with the following parameters: * * - `{Object.}` - route parameters extracted from the current * `$location.path()` by applying the current route templateUrl. * - `{string}` - current `$location.path()` * - `{Object}` - current `$location.search()` * * The custom `redirectTo` function is expected to return a string which will be used * to update `$location.path()` and `$location.search()`. * * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() * changes. * * If the option is set to `false` and url in the browser changes, then * `$routeUpdate` event is broadcasted on the root scope. * * @returns {Object} self * * @description * Adds a new route definition to the `$route` service. */ this.when = function(path, route) { routes[path] = extend({reloadOnSearch: true}, route); // create redirection for trailing slashes if (path) { var redirectPath = (path[path.length-1] == '/') ? path.substr(0, path.length-1) : path +'/'; routes[redirectPath] = {redirectTo: path}; } return this; }; /** * @ngdoc method * @name ng.$routeProvider#otherwise * @methodOf ng.$routeProvider * * @description * Sets route definition that will be used on route change when no other route definition * is matched. * * @param {Object} params Mapping information to be assigned to `$route.current`. * @returns {Object} self */ this.otherwise = function(params) { this.when(null, params); return this; }; this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { /** * @ngdoc object * @name ng.$route * @requires $location * @requires $routeParams * * @property {Object} current Reference to the current route definition. * The route definition contains: * * - `controller`: The controller constructor as define in route definition. * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for * controller instantiation. The `locals` contain * the resolved values of the `resolve` map. Additionally the `locals` also contain: * * - `$scope` - The current route scope. * - `$template` - The current route template HTML. * * @property {Array.} routes Array of all configured routes. * * @description * Is used for deep-linking URLs to controllers and views (HTML partials). * It watches `$location.url()` and tries to map the path to an existing route definition. * * You can define routes through {@link ng.$routeProvider $routeProvider}'s API. * * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView} * directive and the {@link ng.$routeParams $routeParams} service. * * @example This example shows how changing the URL hash causes the `$route` to match a route against the URL, and the `ngView` pulls in the partial. Note that this example is using {@link ng.directive:script inlined templates} to get it working on jsfiddle as well.
Choose: Moby | Moby: Ch1 | Gatsby | Gatsby: Ch4 | Scarlet Letter

$location.path() = {{$location.path()}}
$route.current.templateUrl = {{$route.current.templateUrl}}
$route.current.params = {{$route.current.params}}
$route.current.scope.name = {{$route.current.scope.name}}
$routeParams = {{$routeParams}}
controller: {{name}}
Book Id: {{params.bookId}}
controller: {{name}}
Book Id: {{params.bookId}}
Chapter Id: {{params.chapterId}}
angular.module('ngView', [], function($routeProvider, $locationProvider) { $routeProvider.when('/Book/:bookId', { templateUrl: 'book.html', controller: BookCntl, resolve: { // I will cause a 1 second delay delay: function($q, $timeout) { var delay = $q.defer(); $timeout(delay.resolve, 1000); return delay.promise; } } }); $routeProvider.when('/Book/:bookId/ch/:chapterId', { templateUrl: 'chapter.html', controller: ChapterCntl }); // configure html5 to get links working on jsfiddle $locationProvider.html5Mode(true); }); function MainCntl($scope, $route, $routeParams, $location) { $scope.$route = $route; $scope.$location = $location; $scope.$routeParams = $routeParams; } function BookCntl($scope, $routeParams) { $scope.name = "BookCntl"; $scope.params = $routeParams; } function ChapterCntl($scope, $routeParams) { $scope.name = "ChapterCntl"; $scope.params = $routeParams; } it('should load and compile correct template', function() { element('a:contains("Moby: Ch1")').click(); var content = element('.doc-example-live [ng-view]').text(); expect(content).toMatch(/controller\: ChapterCntl/); expect(content).toMatch(/Book Id\: Moby/); expect(content).toMatch(/Chapter Id\: 1/); element('a:contains("Scarlet")').click(); sleep(2); // promises are not part of scenario waiting content = element('.doc-example-live [ng-view]').text(); expect(content).toMatch(/controller\: BookCntl/); expect(content).toMatch(/Book Id\: Scarlet/); });
*/ /** * @ngdoc event * @name ng.$route#$routeChangeStart * @eventOf ng.$route * @eventType broadcast on root scope * @description * Broadcasted before a route change. At this point the route services starts * resolving all of the dependencies needed for the route change to occurs. * Typically this involves fetching the view template as well as any dependencies * defined in `resolve` route property. Once all of the dependencies are resolved * `$routeChangeSuccess` is fired. * * @param {Route} next Future route information. * @param {Route} current Current route information. */ /** * @ngdoc event * @name ng.$route#$routeChangeSuccess * @eventOf ng.$route * @eventType broadcast on root scope * @description * Broadcasted after a route dependencies are resolved. * {@link ng.directive:ngView ngView} listens for the directive * to instantiate the controller and render the view. * * @param {Object} angularEvent Synthetic event object. * @param {Route} current Current route information. * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. */ /** * @ngdoc event * @name ng.$route#$routeChangeError * @eventOf ng.$route * @eventType broadcast on root scope * @description * Broadcasted if any of the resolve promises are rejected. * * @param {Route} current Current route information. * @param {Route} previous Previous route information. * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. */ /** * @ngdoc event * @name ng.$route#$routeUpdate * @eventOf ng.$route * @eventType broadcast on root scope * @description * * The `reloadOnSearch` property has been set to false, and we are reusing the same * instance of the Controller. */ var forceReload = false, $route = { routes: routes, /** * @ngdoc method * @name ng.$route#reload * @methodOf ng.$route * * @description * Causes `$route` service to reload the current route even if * {@link ng.$location $location} hasn't changed. * * As a result of that, {@link ng.directive:ngView ngView} * creates new scope, reinstantiates the controller. */ reload: function() { forceReload = true; $rootScope.$evalAsync(updateRoute); } }; $rootScope.$on('$locationChangeSuccess', updateRoute); return $route; ///////////////////////////////////////////////////// /** * @param on {string} current url * @param when {string} route when template to match the url against * @return {?Object} */ function switchRouteMatcher(on, when) { // TODO(i): this code is convoluted and inefficient, we should construct the route matching // regex only once and then reuse it // Escape regexp special characters. when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$'; var regex = '', params = [], dst = {}; var re = /:(\w+)/g, paramMatch, lastMatchedIndex = 0; while ((paramMatch = re.exec(when)) !== null) { // Find each :param in `when` and replace it with a capturing group. // Append all other sections of when unchanged. regex += when.slice(lastMatchedIndex, paramMatch.index); regex += '([^\\/]*)'; params.push(paramMatch[1]); lastMatchedIndex = re.lastIndex; } // Append trailing path part. regex += when.substr(lastMatchedIndex); var match = on.match(new RegExp(regex)); if (match) { forEach(params, function(name, index) { dst[name] = match[index + 1]; }); } return match ? dst : null; } function updateRoute() { var next = parseRoute(), last = $route.current; if (next && last && next.$$route === last.$$route && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { last.params = next.params; copy(last.params, $routeParams); $rootScope.$broadcast('$routeUpdate', last); } else if (next || last) { forceReload = false; $rootScope.$broadcast('$routeChangeStart', next, last); $route.current = next; if (next) { if (next.redirectTo) { if (isString(next.redirectTo)) { $location.path(interpolate(next.redirectTo, next.params)).search(next.params) .replace(); } else { $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) .replace(); } } } $q.when(next). then(function() { if (next) { var keys = [], values = [], template; forEach(next.resolve || {}, function(value, key) { keys.push(key); values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); }); if (isDefined(template = next.template)) { } else if (isDefined(template = next.templateUrl)) { template = $http.get(template, {cache: $templateCache}). then(function(response) { return response.data; }); } if (isDefined(template)) { keys.push('$template'); values.push(template); } return $q.all(values).then(function(values) { var locals = {}; forEach(values, function(value, index) { locals[keys[index]] = value; }); return locals; }); } }). // after route change then(function(locals) { if (next == $route.current) { if (next) { next.locals = locals; copy(next.params, $routeParams); } $rootScope.$broadcast('$routeChangeSuccess', next, last); } }, function(error) { if (next == $route.current) { $rootScope.$broadcast('$routeChangeError', next, last, error); } }); } } /** * @returns the current active route, by matching it against the URL */ function parseRoute() { // Match a route var params, match; forEach(routes, function(route, path) { if (!match && (params = switchRouteMatcher($location.path(), path))) { match = inherit(route, { params: extend({}, $location.search(), params), pathParams: params}); match.$$route = route; } }); // No route matched; fallback to "otherwise" route return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); } /** * @returns interpolation of the redirect path with the parametrs */ function interpolate(string, params) { var result = []; forEach((string||'').split(':'), function(segment, i) { if (i == 0) { result.push(segment); } else { var segmentMatch = segment.match(/(\w+)(.*)/); var key = segmentMatch[1]; result.push(params[key]); result.push(segmentMatch[2] || ''); delete params[key]; } }); return result.join(''); } }]; }