diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 4ecc932b427e..53b1927d6ad5 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -17,7 +17,8 @@ */ /* global -ngRouteModule */ var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider); + provider('$route', $RouteProvider), + $routeMinErr = angular.$$minErr('ngRoute'); /** * @ngdoc provider @@ -436,6 +437,36 @@ function $RouteProvider(){ reload: function() { forceReload = true; $rootScope.$evalAsync(updateRoute); + }, + + /** + * @ngdoc method + * @name $route#updateParams + * + * @description + * Causes `$route` service to update the current URL, replacing + * current route parameters with those specified in `newParams`. + * Provided property names that match the route's path segment + * definitions will be interpolated into the location's path, while + * remaining properties will be treated as query params. + * + * @param {Object} newParams mapping of URL parameter names to values + */ + updateParams: function(newParams) { + if (this.current && this.current.$$route) { + var searchParams = {}, self=this; + + angular.forEach(Object.keys(newParams), function(key) { + if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; + }); + + newParams = angular.extend({}, this.current.params, newParams); + $location.path(interpolate(this.current.$$route.originalPath, newParams)); + $location.search(angular.extend({}, $location.search(), searchParams)); + } + else { + throw $routeMinErr('norout', 'Tried updating route when with no current route'); + } } }; diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 072cf6bb8ab7..5dcf96edcb32 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -1046,7 +1046,6 @@ describe('$route', function() { }); }); - describe('reload', function() { it('should reload even if reloadOnSearch is false', function() { @@ -1078,4 +1077,106 @@ describe('$route', function() { }); }); }); + + describe('update', function() { + it('should support single-parameter route updating', function() { + var routeChangeSpy = jasmine.createSpy('route change'); + + module(function($routeProvider) { + $routeProvider.when('/bar/:barId', {controller: angular.noop}); + }); + + inject(function($route, $routeParams, $location, $rootScope) { + $rootScope.$on('$routeChangeSuccess', routeChangeSpy); + + $location.path('/bar/1'); + $rootScope.$digest(); + routeChangeSpy.reset(); + + $route.updateParams({barId: '2'}); + $rootScope.$digest(); + + expect($routeParams).toEqual({barId: '2'}); + expect(routeChangeSpy).toHaveBeenCalledOnce(); + expect($location.path()).toEqual('/bar/2'); + }); + }); + + it('should support total multi-parameter route updating', function() { + var routeChangeSpy = jasmine.createSpy('route change'); + + module(function($routeProvider) { + $routeProvider.when('/bar/:barId/:fooId/:spamId/:eggId', {controller: angular.noop}); + }); + + inject(function($route, $routeParams, $location, $rootScope) { + $rootScope.$on('$routeChangeSuccess', routeChangeSpy); + + $location.path('/bar/1/2/3/4'); + $rootScope.$digest(); + routeChangeSpy.reset(); + + $route.updateParams({barId: '5', fooId: '6', spamId: '7', eggId: '8'}); + $rootScope.$digest(); + + expect($routeParams).toEqual({barId: '5', fooId: '6', spamId: '7', eggId: '8'}); + expect(routeChangeSpy).toHaveBeenCalledOnce(); + expect($location.path()).toEqual('/bar/5/6/7/8'); + }); + }); + + it('should support partial multi-parameter route updating', function() { + var routeChangeSpy = jasmine.createSpy('route change'); + + module(function($routeProvider) { + $routeProvider.when('/bar/:barId/:fooId/:spamId/:eggId', {controller: angular.noop}); + }); + + inject(function($route, $routeParams, $location, $rootScope) { + $rootScope.$on('$routeChangeSuccess', routeChangeSpy); + + $location.path('/bar/1/2/3/4'); + $rootScope.$digest(); + routeChangeSpy.reset(); + + $route.updateParams({barId: '5', fooId: '6'}); + $rootScope.$digest(); + + expect($routeParams).toEqual({barId: '5', fooId: '6', spamId: '3', eggId: '4'}); + expect(routeChangeSpy).toHaveBeenCalledOnce(); + expect($location.path()).toEqual('/bar/5/6/3/4'); + }); + }); + + + it('should update query params when new properties are not in path', function() { + var routeChangeSpy = jasmine.createSpy('route change'); + + module(function($routeProvider) { + $routeProvider.when('/bar/:barId/:fooId/:spamId/', {controller: angular.noop}); + }); + + inject(function($route, $routeParams, $location, $rootScope) { + $rootScope.$on('$routeChangeSuccess', routeChangeSpy); + + $location.path('/bar/1/2/3'); + $location.search({initial: 'true'}); + $rootScope.$digest(); + routeChangeSpy.reset(); + + $route.updateParams({barId: '5', fooId: '6', eggId: '4'}); + $rootScope.$digest(); + + expect($routeParams).toEqual({barId: '5', fooId: '6', spamId: '3', eggId: '4', initial: 'true'}); + expect(routeChangeSpy).toHaveBeenCalledOnce(); + expect($location.path()).toEqual('/bar/5/6/3/'); + expect($location.search()).toEqual({eggId: '4', initial: 'true'}); + }); + }); + + + it('should complain if called without an existing route', inject(function($route) { + expect($route.updateParams).toThrowMinErr('ngRoute', 'norout'); + })); + }); });