From e004378d100ce767a1107180102790a9a360644e Mon Sep 17 00:00:00 2001 From: Igor Minar Date: Mon, 15 Aug 2011 08:34:11 -0700 Subject: [PATCH] feat($route): add reloadOnSearch route param to avoid reloads In order to avoid unnecesary route reloads when just hashSearch part of the url changes, it is now possible to disable this behavior by setting reloadOnSearch param of the route declaration to false. Closes #354 --- src/service/route.js | 29 +++++++- test/service/routeSpec.js | 136 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 3 deletions(-) diff --git a/src/service/route.js b/src/service/route.js index 3d555e4d8fed..2634eb6cd404 100644 --- a/src/service/route.js +++ b/src/service/route.js @@ -68,6 +68,8 @@ angularServiceInject('$route', function(location, $updateView) { matcher = switchRouteMatcher, parentScope = this, dirty = 0, + lastHashPath, + lastRouteParams, $route = { routes: routes, @@ -136,6 +138,18 @@ angularServiceInject('$route', function(location, $updateView) { * 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 @@ -144,8 +158,8 @@ angularServiceInject('$route', function(location, $updateView) { when:function (path, params) { if (isUndefined(path)) return routes; //TODO(im): remove - not needed! var route = routes[path]; - if (!route) route = routes[path] = {}; - if (params) extend(route, params); + if (!route) route = routes[path] = {reloadOnSearch: true}; + if (params) extend(route, params); //TODO(im): what the heck? merge two route definitions? dirty++; return route; }, @@ -209,6 +223,14 @@ angularServiceInject('$route', function(location, $updateView) { 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) { @@ -255,6 +277,7 @@ angularServiceInject('$route', function(location, $updateView) { scope: childScope, params: extend({}, location.hashSearch, pathParams) }); + lastRouteParams = pathParams; } //fire onChange callbacks @@ -266,7 +289,7 @@ angularServiceInject('$route', function(location, $updateView) { } - this.$watch(function(){return dirty + location.hash;}, updateRoute); + this.$watch(function(){ return dirty + location.hash; }, updateRoute); return $route; }, ['$location', '$updateView']); diff --git a/test/service/routeSpec.js b/test/service/routeSpec.js index fc2c7f9d628a..4d24279cde1f 100644 --- a/test/service/routeSpec.js +++ b/test/service/routeSpec.js @@ -227,4 +227,140 @@ describe('$route', function() { } }); }); + + + describe('reloadOnSearch', function() { + it('should reload a route when reloadOnSearch is enabled and hashSearch changes', function() { + var scope = angular.scope(), + $location = scope.$service('$location'), + $route = scope.$service('$route'), + reloaded = jasmine.createSpy('route reload'); + + $route.when('/foo', {controller: FooCtrl}); + $route.onChange(reloaded); + + function FooCtrl() { + reloaded(); + } + + $location.updateHash('/foo'); + scope.$eval(); + expect(reloaded).toHaveBeenCalled(); + reloaded.reset(); + + // trigger reload + $location.hashSearch.foo = 'bar'; + scope.$eval(); + expect(reloaded).toHaveBeenCalled(); + }); + + + it('should not reload a route when reloadOnSearch is disabled and only hashSearch changes', + function() { + var scope = angular.scope(), + $location = scope.$service('$location'), + $route = scope.$service('$route'), + reloaded = jasmine.createSpy('route reload'); + + $route.when('/foo', {controller: FooCtrl, reloadOnSearch: false}); + $route.onChange(reloaded); + + function FooCtrl() { + reloaded(); + } + + expect(reloaded).not.toHaveBeenCalled(); + + $location.updateHash('/foo'); + scope.$eval(); + expect(reloaded).toHaveBeenCalled(); + reloaded.reset(); + + // don't trigger reload + $location.hashSearch.foo = 'bar'; + scope.$eval(); + expect(reloaded).not.toHaveBeenCalled(); + }); + + + it('should reload reloadOnSearch route when url differs only in route path param', function() { + var scope = angular.scope(), + $location = scope.$service('$location'), + $route = scope.$service('$route'), + reloaded = jasmine.createSpy('routeReload'), + onRouteChange = jasmine.createSpy('onRouteChange'); + + $route.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false}); + $route.onChange(onRouteChange); + + function FooCtrl() { + reloaded(); + } + + expect(reloaded).not.toHaveBeenCalled(); + expect(onRouteChange).not.toHaveBeenCalled(); + + $location.updateHash('/foo/aaa'); + scope.$eval(); + expect(reloaded).toHaveBeenCalled(); + expect(onRouteChange).toHaveBeenCalled(); + reloaded.reset(); + onRouteChange.reset(); + + $location.updateHash('/foo/bbb'); + scope.$eval(); + expect(reloaded).toHaveBeenCalled(); + expect(onRouteChange).toHaveBeenCalled(); + reloaded.reset(); + onRouteChange.reset(); + + $location.hashSearch.foo = 'bar'; + scope.$eval(); + expect(reloaded).not.toHaveBeenCalled(); + expect(onRouteChange).not.toHaveBeenCalled(); + }); + + + it('should update route params when reloadOnSearch is disabled and hashSearch', function() { + var scope = angular.scope(), + $location = scope.$service('$location'), + $route = scope.$service('$route'), + routeParams = jasmine.createSpy('routeParams'); + + $route.when('/foo', {controller: FooCtrl}); + $route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false}); + + function FooCtrl() { + this.$watch(function() { + return $route.current.params; + }, function(params) { + routeParams(params); + }); + } + + expect(routeParams).not.toHaveBeenCalled(); + + $location.updateHash('/foo'); + scope.$eval(); + expect(routeParams).toHaveBeenCalledWith({}); + routeParams.reset(); + + // trigger reload + $location.hashSearch.foo = 'bar'; + scope.$eval(); + expect(routeParams).toHaveBeenCalledWith({foo: 'bar'}); + routeParams.reset(); + + $location.updateHash('/bar/123'); + scope.$eval(); + expect(routeParams).toHaveBeenCalledWith({barId: '123'}); + routeParams.reset(); + + // don't trigger reload + $location.hashSearch.foo = 'bar'; + scope.$eval(); + $route.current.scope.$eval(); // ng:view propagates evals so we have to do it by hand here + expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'}); + }); + }); });