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

Commit e986565

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 Closes #14695 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 a84e3e7 commit e986565

File tree

2 files changed

+682
-196
lines changed

2 files changed

+682
-196
lines changed

src/ngRoute/route.js

+118-41
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,12 @@ function $RouteProvider() {
7676
*
7777
* Object properties:
7878
*
79-
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
79+
* - `controller` – `{(string|Function)=}` – Controller fn that should be associated with
8080
* newly created scope or the name of a {@link angular.Module#controller registered
8181
* controller} if passed as a string.
8282
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
8383
* If present, the controller will be published to scope under the `controllerAs` name.
84-
* - `template` – `{string=|function()=}` – html template as a string or a function that
84+
* - `template` – `{(string|Function)=}` – html template as a string or a function that
8585
* returns an html template as a string which should be used by {@link
8686
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
8787
* This property takes precedence over `templateUrl`.
@@ -91,15 +91,15 @@ function $RouteProvider() {
9191
* - `{Array.<Object>}` - route parameters extracted from the current
9292
* `$location.path()` by applying the current route
9393
*
94-
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
94+
* - `templateUrl` – `{(string|Function)=}` – path or function that returns a path to an html
9595
* template that should be used by {@link ngRoute.directive:ngView ngView}.
9696
*
9797
* If `templateUrl` is a function, it will be called with the following parameters:
9898
*
9999
* - `{Array.<Object>}` - route parameters extracted from the current
100100
* `$location.path()` by applying the current route
101101
*
102-
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
102+
* - `resolve` - `{Object.<string, Function>=}` - An optional map of dependencies which should
103103
* be injected into the controller. If any of these dependencies are promises, the router
104104
* will wait for them all to be resolved or one to be rejected before the controller is
105105
* instantiated.
@@ -119,7 +119,7 @@ function $RouteProvider() {
119119
* The map object is:
120120
*
121121
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
122-
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
122+
* - `factory` - `{string|Function}`: If `string` then it is an alias for a service.
123123
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
124124
* and the return value is treated as the dependency. If the result is a promise, it is
125125
* resolved before its value is injected into the controller. Be aware that
@@ -129,7 +129,7 @@ function $RouteProvider() {
129129
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
130130
* the scope of the route. If omitted, defaults to `$resolve`.
131131
*
132-
* - `redirectTo` – `{(string|function())=}` – value to update
132+
* - `redirectTo` – `{(string|Function)=}` – value to update
133133
* {@link ng.$location $location} path with and trigger route redirection.
134134
*
135135
* If `redirectTo` is a function, it will be called with the following parameters:
@@ -140,14 +140,32 @@ function $RouteProvider() {
140140
* - `{Object}` - current `$location.search()`
141141
*
142142
* The custom `redirectTo` function is expected to return a string which will be used
143-
* to update `$location.path()` and `$location.search()`.
143+
* to update `$location.url()`. If the function throws an error, no further processing will
144+
* take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will
145+
* be fired.
144146
*
145147
* Routes that specify `redirectTo` will not have their controllers, template functions
146148
* or resolves called, the `$location` will be changed to the redirect url and route
147149
* processing will stop. The exception to this is if the `redirectTo` is a function that
148150
* returns `undefined`. In this case the route transition occurs as though there was no
149151
* redirection.
150152
*
153+
* - `resolveRedirectTo` – `{Function=}` – a function that will (eventually) return the value
154+
* to update {@link ng.$location $location} URL with and trigger route redirection. In
155+
* contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the
156+
* return value can be either a string or a promise that will be resolved to a string.
157+
*
158+
* Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets
159+
* resolved to `undefined`), no redirection takes place and the route transition occurs as
160+
* though there was no redirection.
161+
*
162+
* If the function throws an error or the returned promise gets rejected, no further
163+
* processing will take place and the
164+
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired.
165+
*
166+
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
167+
* route definition, will cause the latter to be ignored.
168+
*
151169
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
152170
* or `$location.hash()` changes.
153171
*
@@ -452,12 +470,14 @@ function $RouteProvider() {
452470
* @name $route#$routeChangeError
453471
* @eventType broadcast on root scope
454472
* @description
455-
* Broadcasted if any of the resolve promises are rejected.
473+
* Broadcasted if a redirection function fails or any redirection or resolve promises are
474+
* rejected.
456475
*
457476
* @param {Object} angularEvent Synthetic event object
458477
* @param {Route} current Current route information.
459478
* @param {Route} previous Previous route information.
460-
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
479+
* @param {Route} rejection The thrown error or the rejection reason of the promise. Usually
480+
* the rejection reason is the error that caused the promise to get rejected.
461481
*/
462482

463483
/**
@@ -598,44 +618,103 @@ function $RouteProvider() {
598618
} else if (nextRoute || lastRoute) {
599619
forceReload = false;
600620
$route.current = nextRoute;
601-
if (nextRoute) {
602-
if (nextRoute.redirectTo) {
603-
var url = $location.url();
604-
var newUrl;
605-
if (angular.isString(nextRoute.redirectTo)) {
606-
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params))
607-
.search(nextRoute.params)
608-
.replace();
609-
newUrl = $location.url();
610-
} else {
611-
newUrl = nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search());
612-
$location.url(newUrl).replace();
613-
}
614-
if (angular.isDefined(newUrl) && url !== newUrl) {
615-
return; //exit out and don't process current next value, wait for next location change from redirect
616-
}
617-
}
618-
}
619621

620-
$q.when(nextRoute).
621-
then(resolveLocals).
622-
then(function(locals) {
623-
// after route change
624-
if (nextRoute === $route.current) {
625-
if (nextRoute) {
626-
nextRoute.locals = locals;
627-
angular.copy(nextRoute.params, $routeParams);
628-
}
629-
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
630-
}
631-
}, function(error) {
622+
var nextRoutePromise = $q.resolve(nextRoute);
623+
624+
nextRoutePromise.
625+
then(getRedirectionData).
626+
then(handlePossibleRedirection).
627+
then(function(keepProcessingRoute) {
628+
return keepProcessingRoute && nextRoutePromise.
629+
then(resolveLocals).
630+
then(function(locals) {
631+
// after route change
632+
if (nextRoute === $route.current) {
633+
if (nextRoute) {
634+
nextRoute.locals = locals;
635+
angular.copy(nextRoute.params, $routeParams);
636+
}
637+
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
638+
}
639+
});
640+
}).catch(function(error) {
632641
if (nextRoute === $route.current) {
633642
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
634643
}
635644
});
636645
}
637646
}
638647

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

655-
656734
function getTemplateFor(route) {
657735
var template, templateUrl;
658736
if (angular.isDefined(template = route.template)) {
@@ -671,7 +749,6 @@ function $RouteProvider() {
671749
return template;
672750
}
673751

674-
675752
/**
676753
* @returns {Object} the current active route, by matching it against the URL
677754
*/

0 commit comments

Comments
 (0)