diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 44da57d64fdf..3f1e2b27b487 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -364,6 +364,21 @@ function $RouteProvider(){ * */ + /** + * @ngdoc event + * @name $route#$beforeRouteChange + * @eventType broadcast on root scope + * + * @description + * Broadcasted during locationChangeStart, will cancel $locationChangeStart + * if preventDefault() is called, thus preventing subsequent $routeChange + * events. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} nextRoute route information of the future route. + * @param {Route} currentRoute current route information. + */ + /** * @ngdoc event * @name $route#$routeChangeStart @@ -469,6 +484,7 @@ function $RouteProvider(){ } }; + $rootScope.$on('$locationChangeStart', beforeUpdateRoute); $rootScope.$on('$locationChangeSuccess', updateRoute); return $route; @@ -582,6 +598,18 @@ function $RouteProvider(){ } + // $locationChangeStart handler. Dispatches $beforeRouteChange, and cancels $locationChangeStart + // if the user cancels the $beforeRouteChange event. + function beforeUpdateRoute(event, next, current) { + var nextRoute = parseRoute(); + var lastRoute = $route.current; + + if ($rootScope.$broadcast('$beforeRouteChange', nextRoute, lastRoute).defaultPrevented) { + event.preventDefault(); + } + } + + /** * @returns {Object} the current active route, by matching it against the URL */ diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 8a0a370f0615..8b9ede967b3b 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -196,7 +196,7 @@ describe('$route', function() { event.preventDefault(); }); - $rootScope.$on('$beforeRouteChange', function(event) { + $rootScope.$on('$routeChangeStart', function(event) { throw new Error('Should not get here'); }); @@ -741,6 +741,65 @@ describe('$route', function() { expect($exceptionHandler.errors).toEqual([myError]); }); }); + + + it('should pass nextRoute and currentRoute to $beforeRouteChange', function() { + module(function($routeProvider) { + $routeProvider.when('/a', { + template: '

route A

' + }).when('/b', { + template: '

route B

' + }); + }); + + inject(function($location, $route, $rootScope) { + var beforeRouteChangeCalled = false; + $location.path('/a'); + $rootScope.$digest(); + + $rootScope.$on('$beforeRouteChange', function(event, nextRoute, currentRoute) { + beforeRouteChangeCalled = true; + expect(nextRoute.template).toBe('

route B

'); + expect(currentRoute.template).toBe('

route A

'); + }); + + $location.path('/b'); + $rootScope.$digest(); + + expect(beforeRouteChangeCalled).toBe(true); + }); + }); + + + it('should cancel $locationChange when $beforeRouteChange is cancelled', function() { + module(function($routeProvider) { + $routeProvider.when('/a', { + template: '

route A

' + }).when('/b', { + template: '

route B

' + }); + }); + + inject(function($location, $route, $rootScope) { + var didChangeLocation = false; + $location.path('/a'); + $rootScope.$digest(); + + $rootScope.$on('$beforeRouteChange', function(event, nextRoute, currentRoute) { + event.preventDefault(); + }); + $rootScope.$on('$locationChangeSuccess', function() { + didChangeLocation = true; + }); + + $location.path('/b'); + $rootScope.$digest(); + + expect(didChangeLocation).toBe(false); + expect($location.path()).toBe('/a'); + expect($route.current.template).toBe('

route A

'); + }); + }); });