diff options
| author | Misko Hevery | 2011-08-23 22:30:14 -0700 |
|---|---|---|
| committer | Igor Minar | 2011-08-31 14:31:23 -0700 |
| commit | ad3cc16eef0a13844e6e05abcb18c46a370f0814 (patch) | |
| tree | 48c90bd555d26eef0164b7deeff5417e300a6536 /src/service | |
| parent | 08d09ecbaa07564bf3cf6a62e0be4c41b355d23b (diff) | |
| download | angular.js-ad3cc16eef0a13844e6e05abcb18c46a370f0814.tar.bz2 | |
feat($route): add events before/after route change
BREAKING CHANGE
* removing `onChange`
FEATURE
* adding three events: $beforeRouteChange, $afterRouteChange, $routeReload
Diffstat (limited to 'src/service')
| -rw-r--r-- | src/service/route.js | 242 | ||||
| -rw-r--r-- | src/service/routeParams.js | 31 |
2 files changed, 170 insertions, 103 deletions
diff --git a/src/service/route.js b/src/service/route.js index b7e4814b..523a244a 100644 --- a/src/service/route.js +++ b/src/service/route.js @@ -5,6 +5,7 @@ * @ngdoc service * @name angular.service.$route * @requires $location + * @requires $routeParams * * @property {Object} current Reference to the current route definition. * @property {Array.<Object>} routes Array of all configured routes. @@ -14,7 +15,7 @@ * 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. + * widget and the {@link angular.service.$routeParams $routeParams} service. * * @example This example shows how changing the URL hash causes the <tt>$route</tt> @@ -24,23 +25,23 @@ <doc:example> <doc:source jsfiddle="false"> <script> - function MainCntl($route, $location) { + function MainCntl($route, $routeParams, $location) { this.$route = $route; this.$location = $location; + this.$routeParams = $routeParams; $route.when('/Book/:bookId', {template: 'examples/book.html', controller: BookCntl}); $route.when('/Book/:bookId/ch/:chapterId', {template: 'examples/chapter.html', controller: ChapterCntl}); - $route.onChange(function() { - $route.current.scope.params = $route.current.params; - }); } - function BookCntl() { + function BookCntl($routeParams) { this.name = "BookCntl"; + this.params = $routeParams; } - function ChapterCntl() { + function ChapterCntl($routeParams) { this.name = "ChapterCntl"; + this.params = $routeParams; } </script> @@ -54,6 +55,7 @@ <pre>$route.current.template = {{$route.current.template}}</pre> <pre>$route.current.params = {{$route.current.params}}</pre> <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> + <pre>$routeParams = {{$routeParams}}</pre> <hr /> <ng:view></ng:view> </div> @@ -62,37 +64,69 @@ </doc:scenario> </doc:example> */ -angularServiceInject('$route', function($location) { +angularServiceInject('$route', function($location, $routeParams) { + /** + * @workInProgress + * @ngdoc event + * @name angular.service.$route#$beforeRouteChange + * @eventOf angular.service.$route + * @eventType Broadcast on root scope + * @description + * Broadcasted before a route change. + * + * @param {Route} next Future route information. + * @param {Route} current Current route information. + * + * The `Route` object extends the route definition with the following properties. + * + * * `scope` - The instance of the route controller. + * * `params` - The current {@link angular.service.$routeParams params}. + * + */ + + /** + * @workInProgress + * @ngdoc event + * @name angular.service.$route#$afterRouteChange + * @eventOf angular.service.$route + * @eventType Broadcast on root scope + * @description + * Broadcasted after a route change. + * + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * + * The `Route` object extends the route definition with the following properties. + * + * * `scope` - The instance of the route controller. + * * `params` - The current {@link angular.service.$routeParams params}. + * + */ + + /** + * @workInProgress + * @ngdoc event + * @name angular.service.$route#$routeUpdate + * @eventOf angular.service.$route + * @eventType Emit on the current route scope. + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + var routes = {}, - onChange = [], matcher = switchRouteMatcher, parentScope = this, + rootScope = this, dirty = 0, - lastHashPath, - lastRouteParams, + allowReload = true, $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 * @@ -114,7 +148,7 @@ angularServiceInject('$route', function($location) { * @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 + * @param {Object} route Mapping information to be assigned to `$route.current` on route * match. * * Object properties: @@ -139,14 +173,15 @@ angularServiceInject('$route', function($location) { * 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: + * changes. + * + * If the option is set to false and url in the browser changes, then + * $routeUpdate event is emited on the current route scope. You can use this event to + * react to {@link angular.service.$routeParams} changes: * - * function MyCtrl($route) { - * this.$watch(function() { - * return $route.current.params; - * }, function(scope, params) { - * //do stuff with params + * function MyCtrl($route, $routeParams) { + * this.$on('$routeUpdate', function() { + * // do stuff with $routeParams * }); * } * @@ -155,13 +190,13 @@ angularServiceInject('$route', function($location) { * @description * Adds a new route definition to the `$route` service. */ - when:function (path, params) { + when:function (path, route) { 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? + var routeDef = routes[path]; + if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true}; + if (route) extend(routeDef, route); //TODO(im): what the heck? merge two route definitions? dirty++; - return route; + return routeDef; }, /** @@ -192,10 +227,18 @@ angularServiceInject('$route', function($location) { */ reload: function() { dirty++; + allowReload = false; } }; + + this.$watch(function(){ return dirty + $location.hash; }, updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + function switchRouteMatcher(on, when, dstName) { var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', params = [], @@ -219,79 +262,72 @@ angularServiceInject('$route', function($location) { return match ? dst : null; } - function updateRoute(){ - var selectedRoute, pathParams, segmentMatch, key, redir; + var next = parseRoute(), + last = $route.current; - if ($route.current) { - if (!$route.current.reloadOnSearch && (lastHashPath == $location.hashPath)) { - $route.current.params = extend($location.hashSearch, lastRouteParams); - return; - } - - if ($route.current.scope) { - $route.current.scope.$destroy(); + if (next && last && next.$route === last.$route + && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && allowReload) { + $route.current = next; + copy(next.params, $routeParams); + last.scope && last.scope.$emit('$routeUpdate'); + } else { + allowReload = true; + rootScope.$broadcast('$beforeRouteChange', next, last); + last && last.scope && last.scope.$destroy(); + $route.current = next; + if (next) { + if (next.redirectTo) { + $location.update(isString(next.redirectTo) + ? {hashSearch: next.params, hashPath: interpolate(next.redirectTo, next.params)} + : {hash: next.redirectTo(next.pathParams, + $location.hash, $location.hashPath, $location.hashSearch)}); + } else { + copy(next.params, $routeParams); + next.scope = parentScope.$new(next.controller); + } } + rootScope.$broadcast('$afterRouteChange', next, last); } + } + - lastHashPath = $location.hashPath; - $route.current = null; + /** + * @returns the current active route, by matching it against the URL + */ + function parseRoute(){ // Match a route - forEach(routes, function(rParams, rPath) { - if (!pathParams) { - if ((pathParams = matcher($location.hashPath, rPath))) { - selectedRoute = rParams; - } + var params, match; + forEach(routes, function(route, path) { + if (!match && (params = matcher($location.hashPath, path))) { + match = inherit(route, { + params: extend({}, $location.hashSearch, params), + pathParams: params}); + match.$route = route; } }); - // No route matched; fallback to "otherwise" route - selectedRoute = selectedRoute || routes[null]; - - if(selectedRoute) { - if (selectedRoute.redirectTo) { - if (isString(selectedRoute.redirectTo)) { - // interpolate the redirectTo string - redir = {hashPath: '', - hashSearch: extend({}, $location.hashSearch, pathParams)}; - - forEach(selectedRoute.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: selectedRoute.redirectTo(pathParams, $location.hash, $location.hashPath, - $location.hashSearch)}; - } + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } - $location.update(redir); - return; + /** + * @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]; } - - $route.current = extend({}, selectedRoute); - $route.current.params = extend({}, $location.hashSearch, pathParams); - lastRouteParams = pathParams; - } - - //fire onChange callbacks - forEach(onChange, parentScope.$eval, parentScope); - - // Create the scope if we have matched a route - if ($route.current) { - $route.current.scope = parentScope.$new($route.current.controller); - } + }); + return result.join(''); } - this.$watch(function(){ return dirty + $location.hash; }, updateRoute); - - return $route; -}, ['$location']); +}, ['$location', '$routeParams']); diff --git a/src/service/routeParams.js b/src/service/routeParams.js new file mode 100644 index 00000000..8a69903f --- /dev/null +++ b/src/service/routeParams.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * @workInProgress + * @ngdoc service + * @name angular.service.$routeParams + * @requires $route + * + * @description + * Current set of route parameters. The route parameters are a combination of the + * {@link angular.service.$location $location} `hashSearch`, and `path`. The `path` parameters + * are extracted when the {@link angular.service.$route $route} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `hashSearch` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * @example + * <pre> + * // Given: + * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby + * // Route: /Chapter/:chapterId/Section/:sectionId + * // + * // Then + * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} + * </pre> + */ +angularService('$routeParams', function(){ + return {}; +}); |
