Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat($route): ability to cancel $routeChangeStart event #9502

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 56 additions & 35 deletions src/ngRoute/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,8 @@ function $RouteProvider(){
*/

var forceReload = false,
preparedRoute,
preparedRouteIsUpdateOnly,
$route = {
routes: routes,

Expand All @@ -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();
});
},

/**
Expand Down Expand Up @@ -469,7 +475,8 @@ function $RouteProvider(){
}
};

$rootScope.$on('$locationChangeSuccess', updateRoute);
$rootScope.$on('$locationChangeStart', prepareRoute);
$rootScope.$on('$locationChangeSuccess', commitRoute);

return $route;

Expand Down Expand Up @@ -507,54 +514,68 @@ 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) {
locals[key] = angular.isString(value) ?
$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);
}
}
Expand All @@ -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);
}
});
}
Expand Down
60 changes: 58 additions & 2 deletions test/ngRoute/routeSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';

describe('$route', function() {
var $httpBackend;
var $httpBackend,
element;

beforeEach(module('ngRoute'));

Expand All @@ -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('<div><div ng-view></div></div>')($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,
Expand Down Expand Up @@ -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() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success ? (capital S)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for catching this. Could you create a PR to fix it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var routeChangeSpy = jasmine.createSpy('route change');

module(function($routeProvider) {
Expand All @@ -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();
});
});

Expand Down