'use strict'; /** * @workInProgress * @ngdoc service * @name angular.service.$route * @requires $location * * @property {Object} current Reference to the current route definition. * @property {Array.} routes Array of all configured routes. * * @description * Watches `$location.hashPath` and tries to map the hash to an existing route * definition. It is used for deep-linking URLs to controllers and views (HTML partials). * * The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view} * widget. * * @example This example shows how changing the URL hash causes the $route to match a route against the URL, and the [[ng:include]] pulls in the partial. Try changing the URL in the input box to see changes.
Choose: Moby | Moby: Ch1 | Gatsby | Gatsby: Ch4
$location.hashPath:
$route.current.template = {{$route.current.template}}
$route.current.params = {{$route.current.params}}
$route.current.scope.name = {{$route.current.scope.name}}

*/ angularServiceInject('$route', function(location, $updateView) { var routes = {}, onChange = [], matcher = switchRouteMatcher, parentScope = this, dirty = 0, lastHashPath, lastRouteParams, $route = { routes: routes, /** * @workInProgress * @ngdoc method * @name angular.service.$route#onChange * @methodOf angular.service.$route * * @param {function()} fn Function that will be called when `$route.current` changes. * @returns {function()} The registered function. * * @description * Register a handler function that will be called when route changes */ onChange: function(fn) { onChange.push(fn); return fn; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#parent * @methodOf angular.service.$route * * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created * `$route.current.scope` scopes. * * @description * Sets a scope to be used as the parent scope for scopes created on route change. If not * set, defaults to the root scope. */ parent: function(scope) { if (scope) parentScope = scope; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#when * @methodOf angular.service.$route * * @param {string} path Route path (matched against `$location.hash`) * @param {Object} params Mapping information to be assigned to `$route.current` on route * match. * * Object properties: * * - `controller` – `{function()=}` – Controller fn that should be associated with newly * created scope. * - `template` – `{string=}` – path to an html template that should be used by * {@link angular.widget.ng:view ng:view} or * {@link angular.widget.ng:include ng:include} widgets. * - `redirectTo` – {(string|function())=} – value to update * {@link angular.service.$location $location} hash 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.hashPath` by applying the current route template. * - `{string}` - current `$location.hash` * - `{string}` - current `$location.hashPath` * - `{string}` - current `$location.hashSearch` * * The custom `redirectTo` function is expected to return a string which will be used * to update `$location.hash`. * * - `[reloadOnSearch=true]` - {boolean=} - reload route when $location.hashSearch * changes. If this option is disabled, you should set up a $watch to be notified of * param (hashSearch) changes as follows: * * function MyCtrl($route) { * this.$watch(function() { * return $route.current.params.myHashSearchParam; * }, function(params) { * //do stuff with params * }); * } * * @returns {Object} route object * * @description * Adds a new route definition to the `$route` service. */ when:function (path, params) { if (isUndefined(path)) return routes; //TODO(im): remove - not needed! var route = routes[path]; if (!route) route = routes[path] = {reloadOnSearch: true}; if (params) extend(route, params); //TODO(im): what the heck? merge two route definitions? dirty++; return route; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#otherwise * @methodOf angular.service.$route * * @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`. */ otherwise: function(params) { $route.when(null, params); }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#reload * @methodOf angular.service.$route * * @description * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next * eval even if {@link angular.service.$location $location} hasn't changed. */ reload: function() { dirty++; } }; function switchRouteMatcher(on, when, dstName) { var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', params = [], dst = {}; forEach(when.split(/\W/), function(param){ if (param) { var paramRegExp = new RegExp(":" + param + "([\\W])"); if (regex.match(paramRegExp)) { regex = regex.replace(paramRegExp, "([^\/]*)$1"); params.push(param); } } }); var match = on.match(new RegExp(regex)); if (match) { forEach(params, function(name, index){ dst[name] = match[index + 1]; }); if (dstName) this.$set(dstName, dst); } return match ? dst : null; } function updateRoute(){ var childScope, routeParams, pathParams, segmentMatch, key, redir; if ($route.current) { if (!$route.current.reloadOnSearch && (lastHashPath == location.hashPath)) { $route.current.params = extend({}, location.hashSearch, lastRouteParams); return; } } lastHashPath = location.hashPath; $route.current = null; forEach(routes, function(rParams, rPath) { if (!pathParams) { if (pathParams = matcher(location.hashPath, rPath)) { routeParams = rParams; } } }); // "otherwise" fallback routeParams = routeParams || routes[null]; if(routeParams) { if (routeParams.redirectTo) { if (isString(routeParams.redirectTo)) { // interpolate the redirectTo string redir = {hashPath: '', hashSearch: extend({}, location.hashSearch, pathParams)}; forEach(routeParams.redirectTo.split(':'), function(segment, i) { if (i==0) { redir.hashPath += segment; } else { segmentMatch = segment.match(/(\w+)(.*)/); key = segmentMatch[1]; redir.hashPath += pathParams[key] || location.hashSearch[key]; redir.hashPath += segmentMatch[2] || ''; delete redir.hashSearch[key]; } }); } else { // call custom redirectTo function redir = {hash: routeParams.redirectTo(pathParams, location.hash, location.hashPath, location.hashSearch)}; } location.update(redir); $updateView(); //TODO this is to work around the $location<=>$browser issues return; } childScope = createScope(parentScope); $route.current = extend({}, routeParams, { scope: childScope, params: extend({}, location.hashSearch, pathParams) }); lastRouteParams = pathParams; } //fire onChange callbacks forEach(onChange, parentScope.$tryEval); if (childScope) { childScope.$become($route.current.controller); } } this.$watch(function(){ return dirty + location.hash; }, updateRoute); return $route; }, ['$location', '$updateView']);