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

Commit 0f5c1cf

Browse files
committed
feat($route): implement resolveRedirectTo
`resolveRedirectTo` can be set to a function that will (eventually) return the new URL to redirect to. The function supports dependency injection and should return the new URL either as a string or as a promise that will be resolved to a string. If the `resolveRedirectTo` function returns `undefined` or returns a promise that gets resolved to `undefined`, no redirection will take place and the current route will be processed normally. If the `resolveRedirectTo` function throws an error or the returned promise gets rejected, no further processing will take place (e.g. no template fetched, no `resolve` functions run, no controller instantiated) and a `$routeChangeError` will be broadcasted. `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same route definition, will cause the latter to be ignored. Fixes #5150 BREAKING CHANGE: Previously, if `redirectTo` was a function that threw an Error, execution was aborted without firing a `$routeChangeError` event. Now, if a `redirectTo` function throws an Error, a `$routeChangeError` event will be fired.
1 parent cfc8b41 commit 0f5c1cf

File tree

2 files changed

+682
-196
lines changed

2 files changed

+682
-196
lines changed

src/ngRoute/route.js

Lines changed: 118 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ function $RouteProvider() {
7070
*
7171
* Object properties:
7272
*
73-
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
73+
* - `controller` – `{(string|Function)=}` – Controller fn that should be associated with
7474
* newly created scope or the name of a {@link angular.Module#controller registered
7575
* controller} if passed as a string.
7676
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
7777
* If present, the controller will be published to scope under the `controllerAs` name.
78-
* - `template` – `{string=|function()=}` – html template as a string or a function that
78+
* - `template` – `{(string|Function)=}` – html template as a string or a function that
7979
* returns an html template as a string which should be used by {@link
8080
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
8181
* This property takes precedence over `templateUrl`.
@@ -85,15 +85,15 @@ function $RouteProvider() {
8585
* - `{Array.<Object>}` - route parameters extracted from the current
8686
* `$location.path()` by applying the current route
8787
*
88-
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
88+
* - `templateUrl` – `{(string|Function)=}` – path or function that returns a path to an html
8989
* template that should be used by {@link ngRoute.directive:ngView ngView}.
9090
*
9191
* If `templateUrl` is a function, it will be called with the following parameters:
9292
*
9393
* - `{Array.<Object>}` - route parameters extracted from the current
9494
* `$location.path()` by applying the current route
9595
*
96-
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
96+
* - `resolve` - `{Object.<string, Function>=}` - An optional map of dependencies which should
9797
* be injected into the controller. If any of these dependencies are promises, the router
9898
* will wait for them all to be resolved or one to be rejected before the controller is
9999
* instantiated.
@@ -113,7 +113,7 @@ function $RouteProvider() {
113113
* The map object is:
114114
*
115115
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
116-
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
116+
* - `factory` - `{string|Function}`: If `string` then it is an alias for a service.
117117
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
118118
* and the return value is treated as the dependency. If the result is a promise, it is
119119
* resolved before its value is injected into the controller. Be aware that
@@ -123,7 +123,7 @@ function $RouteProvider() {
123123
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
124124
* the scope of the route. If omitted, defaults to `$resolve`.
125125
*
126-
* - `redirectTo` – `{(string|function())=}` – value to update
126+
* - `redirectTo` – `{(string|Function)=}` – value to update
127127
* {@link ng.$location $location} path with and trigger route redirection.
128128
*
129129
* If `redirectTo` is a function, it will be called with the following parameters:
@@ -134,14 +134,32 @@ function $RouteProvider() {
134134
* - `{Object}` - current `$location.search()`
135135
*
136136
* The custom `redirectTo` function is expected to return a string which will be used
137-
* to update `$location.path()` and `$location.search()`.
137+
* to update `$location.url()`. If the function throws an error, no further processing will
138+
* take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will
139+
* be fired.
138140
*
139141
* Routes that specify `redirectTo` will not have their controllers, template functions
140142
* or resolves called, the `$location` will be changed to the redirect url and route
141143
* processing will stop. The exception to this is if the `redirectTo` is a function that
142144
* returns `undefined`. In this case the route transition occurs as though there was no
143145
* redirection.
144146
*
147+
* - `resolveRedirectTo` – `{Function=}` – a function that will (eventually) return the value
148+
* to update {@link ng.$location $location} URL with and trigger route redirection. In
149+
* contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the
150+
* return value can be either a string or a promise that will be resolved to a string.
151+
*
152+
* Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets
153+
* resolved to `undefined`), no redirection takes place and the route transition occurs as
154+
* though there was no redirection.
155+
*
156+
* If the function throws an error or the returned promise gets rejected, no further
157+
* processing will take place and the
158+
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired.
159+
*
160+
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
161+
* route definition, will cause the latter to be ignored.
162+
*
145163
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
146164
* or `$location.hash()` changes.
147165
*
@@ -446,12 +464,14 @@ function $RouteProvider() {
446464
* @name $route#$routeChangeError
447465
* @eventType broadcast on root scope
448466
* @description
449-
* Broadcasted if any of the resolve promises are rejected.
467+
* Broadcasted if a redirection function fails or any redirection or resolve promises are
468+
* rejected.
450469
*
451470
* @param {Object} angularEvent Synthetic event object
452471
* @param {Route} current Current route information.
453472
* @param {Route} previous Previous route information.
454-
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
473+
* @param {Route} rejection The thrown error or the rejection reason of the promise. Usually
474+
* the rejection reason is the error that caused the promise to get rejected.
455475
*/
456476

457477
/**
@@ -592,44 +612,103 @@ function $RouteProvider() {
592612
} else if (nextRoute || lastRoute) {
593613
forceReload = false;
594614
$route.current = nextRoute;
595-
if (nextRoute) {
596-
if (nextRoute.redirectTo) {
597-
var url = $location.url();
598-
var newUrl;
599-
if (angular.isString(nextRoute.redirectTo)) {
600-
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params))
601-
.search(nextRoute.params)
602-
.replace();
603-
newUrl = $location.url();
604-
} else {
605-
newUrl = nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search());
606-
$location.url(newUrl).replace();
607-
}
608-
if (angular.isDefined(newUrl) && url !== newUrl) {
609-
return; //exit out and don't process current next value, wait for next location change from redirect
610-
}
611-
}
612-
}
613615

614-
$q.when(nextRoute).
615-
then(resolveLocals).
616-
then(function(locals) {
617-
// after route change
618-
if (nextRoute === $route.current) {
619-
if (nextRoute) {
620-
nextRoute.locals = locals;
621-
angular.copy(nextRoute.params, $routeParams);
622-
}
623-
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
624-
}
625-
}, function(error) {
616+
var nextRoutePromise = $q.resolve(nextRoute);
617+
618+
nextRoutePromise.
619+
then(getRedirectionData).
620+
then(handlePossibleRedirection).
621+
then(function(keepProcessingRoute) {
622+
return keepProcessingRoute && nextRoutePromise.
623+
then(resolveLocals).
624+
then(function(locals) {
625+
// after route change
626+
if (nextRoute === $route.current) {
627+
if (nextRoute) {
628+
nextRoute.locals = locals;
629+
angular.copy(nextRoute.params, $routeParams);
630+
}
631+
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
632+
}
633+
});
634+
}).catch(function(error) {
626635
if (nextRoute === $route.current) {
627636
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
628637
}
629638
});
630639
}
631640
}
632641

642+
function getRedirectionData(route) {
643+
var data = {
644+
route: route,
645+
hasRedirection: false
646+
};
647+
648+
if (route) {
649+
if (route.redirectTo) {
650+
if (angular.isString(route.redirectTo)) {
651+
data.path = interpolate(route.redirectTo, route.params);
652+
data.search = route.params;
653+
data.hasRedirection = true;
654+
} else {
655+
var oldPath = $location.path();
656+
var oldSearch = $location.search();
657+
var newUrl = route.redirectTo(route.pathParams, oldPath, oldSearch);
658+
659+
if (angular.isDefined(newUrl)) {
660+
data.url = newUrl;
661+
data.hasRedirection = true;
662+
}
663+
}
664+
} else if (route.resolveRedirectTo) {
665+
return $q.
666+
resolve($injector.invoke(route.resolveRedirectTo)).
667+
then(function(newUrl) {
668+
if (angular.isDefined(newUrl)) {
669+
data.url = newUrl;
670+
data.hasRedirection = true;
671+
}
672+
673+
return data;
674+
});
675+
}
676+
}
677+
678+
return data;
679+
}
680+
681+
function handlePossibleRedirection(data) {
682+
var keepProcessingRoute = true;
683+
684+
if (data.route !== $route.current) {
685+
keepProcessingRoute = false;
686+
} else if (data.hasRedirection) {
687+
var oldUrl = $location.url();
688+
var newUrl = data.url;
689+
690+
if (newUrl) {
691+
$location.
692+
url(newUrl).
693+
replace();
694+
} else {
695+
newUrl = $location.
696+
path(data.path).
697+
search(data.search).
698+
replace().
699+
url();
700+
}
701+
702+
if (newUrl !== oldUrl) {
703+
// Exit out and don't process current next value,
704+
// wait for next location change from redirect
705+
keepProcessingRoute = false;
706+
}
707+
}
708+
709+
return keepProcessingRoute;
710+
}
711+
633712
function resolveLocals(route) {
634713
if (route) {
635714
var locals = angular.extend({}, route.resolve);
@@ -646,7 +725,6 @@ function $RouteProvider() {
646725
}
647726
}
648727

649-
650728
function getTemplateFor(route) {
651729
var template, templateUrl;
652730
if (angular.isDefined(template = route.template)) {
@@ -665,7 +743,6 @@ function $RouteProvider() {
665743
return template;
666744
}
667745

668-
669746
/**
670747
* @returns {Object} the current active route, by matching it against the URL
671748
*/

0 commit comments

Comments
 (0)