diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index 44da57d64fdf..11e6a2b829fa 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -419,6 +419,8 @@ function $RouteProvider(){ */ var forceReload = false, + preparedRoute, + preparedRouteIsUpdateOnly, $route = { routes: routes, @@ -435,7 +437,11 @@ function $RouteProvider(){ */ reload: function() { forceReload = true; - $rootScope.$evalAsync(updateRoute); + $rootScope.$evalAsync(function() { + // Don't support cancellation of a reload for now... + prepareRoute(); + commitRoute(); + }); }, /** @@ -469,7 +475,8 @@ function $RouteProvider(){ } }; - $rootScope.$on('$locationChangeSuccess', updateRoute); + $rootScope.$on('$locationChangeStart', prepareRoute); + $rootScope.$on('$locationChangeSuccess', commitRoute); return $route; @@ -507,36 +514,50 @@ function $RouteProvider(){ return params; } - function updateRoute() { - var next = parseRoute(), - last = $route.current; - - if (next && last && next.$$route === last.$$route - && angular.equals(next.pathParams, last.pathParams) - && !next.reloadOnSearch && !forceReload) { - last.params = next.params; - angular.copy(last.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', last); - } else if (next || last) { + function prepareRoute($locationEvent) { + var lastRoute = $route.current; + + preparedRoute = parseRoute(); + preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route + && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) + && !preparedRoute.reloadOnSearch && !forceReload; + + if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { + if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { + if ($locationEvent) { + $locationEvent.preventDefault(); + } + } + } + } + + function commitRoute() { + var lastRoute = $route.current; + var nextRoute = preparedRoute; + + if (preparedRouteIsUpdateOnly) { + lastRoute.params = nextRoute.params; + angular.copy(lastRoute.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', lastRoute); + } else if (nextRoute || lastRoute) { forceReload = false; - $rootScope.$broadcast('$routeChangeStart', next, last); - $route.current = next; - if (next) { - if (next.redirectTo) { - if (angular.isString(next.redirectTo)) { - $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + $route.current = nextRoute; + if (nextRoute) { + if (nextRoute.redirectTo) { + if (angular.isString(nextRoute.redirectTo)) { + $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) .replace(); } else { - $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) .replace(); } } } - $q.when(next). + $q.when(nextRoute). then(function() { - if (next) { - var locals = angular.extend({}, next.resolve), + if (nextRoute) { + var locals = angular.extend({}, nextRoute.resolve), template, templateUrl; angular.forEach(locals, function(value, key) { @@ -544,17 +565,17 @@ function $RouteProvider(){ $injector.get(value) : $injector.invoke(value, null, null, key); }); - if (angular.isDefined(template = next.template)) { + if (angular.isDefined(template = nextRoute.template)) { if (angular.isFunction(template)) { - template = template(next.params); + template = template(nextRoute.params); } - } else if (angular.isDefined(templateUrl = next.templateUrl)) { + } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(next.params); + templateUrl = templateUrl(nextRoute.params); } templateUrl = $sce.getTrustedResourceUrl(templateUrl); if (angular.isDefined(templateUrl)) { - next.loadedTemplateUrl = templateUrl; + nextRoute.loadedTemplateUrl = templateUrl; template = $templateRequest(templateUrl); } } @@ -566,16 +587,16 @@ function $RouteProvider(){ }). // after route change then(function(locals) { - if (next == $route.current) { - if (next) { - next.locals = locals; - angular.copy(next.params, $routeParams); + if (nextRoute == $route.current) { + if (nextRoute) { + nextRoute.locals = locals; + angular.copy(nextRoute.params, $routeParams); } - $rootScope.$broadcast('$routeChangeSuccess', next, last); + $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); } }, function(error) { - if (next == $route.current) { - $rootScope.$broadcast('$routeChangeError', next, last, error); + if (nextRoute == $route.current) { + $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); } }); } diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 8a0a370f0615..d15b65d1792e 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -1,7 +1,8 @@ 'use strict'; describe('$route', function() { - var $httpBackend; + var $httpBackend, + element; beforeEach(module('ngRoute')); @@ -18,6 +19,57 @@ describe('$route', function() { }; })); + afterEach(function() { + dealoc(element); + }); + + it('should allow cancellation via $locationChangeStart via $routeChangeStart', function() { + module(function($routeProvider) { + $routeProvider.when('/Edit', { + id: 'edit', template: 'Some edit functionality' + }); + $routeProvider.when('/Home', { + id: 'home' + }); + }); + module(provideLog); + inject(function($route, $location, $rootScope, $compile, log) { + $rootScope.$on('$routeChangeStart', function(event, next, current) { + if (next.id === 'home' && current.scope.unsavedChanges) { + event.preventDefault(); + } + }); + element = $compile('
')($rootScope); + $rootScope.$apply(function() { + $location.path('/Edit'); + }); + $rootScope.$on('$routeChangeSuccess', log.fn('routeChangeSuccess')); + $rootScope.$on('$locationChangeSuccess', log.fn('locationChangeSuccess')); + + // aborted route change + $rootScope.$apply(function() { + $route.current.scope.unsavedChanges = true; + }); + $rootScope.$apply(function() { + $location.path('/Home'); + }); + expect($route.current.id).toBe('edit'); + expect($location.path()).toBe('/Edit'); + expect(log).toEqual([]); + + // successful route change + $rootScope.$apply(function() { + $route.current.scope.unsavedChanges = false; + }); + $rootScope.$apply(function() { + $location.path('/Home'); + }); + expect($route.current.id).toBe('home'); + expect($location.path()).toBe('/Home'); + expect(log).toEqual(['locationChangeSuccess', 'routeChangeSuccess']); + }); + }); + it('should route and fire change event', function() { var log = '', lastRoute, @@ -481,7 +533,7 @@ describe('$route', function() { describe('events', function() { - it('should not fire $after/beforeRouteChange during bootstrap (if no route)', function() { + it('should not fire $routeChangeStart/success during bootstrap (if no route)', function() { var routeChangeSpy = jasmine.createSpy('route change'); module(function($routeProvider) { @@ -498,6 +550,10 @@ describe('$route', function() { $location.path('/no-route-here'); $rootScope.$digest(); expect(routeChangeSpy).not.toHaveBeenCalled(); + + $location.path('/one'); + $rootScope.$digest(); + expect(routeChangeSpy).toHaveBeenCalled(); }); });