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

feat($route): implement resolveRedirectTo #14695

Closed
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
159 changes: 118 additions & 41 deletions src/ngRoute/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ function $RouteProvider() {
*
* Object properties:
*
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with
* - `controller` – `{(string|Function)=}` – Controller fn that should be associated with
* newly created scope or the name of a {@link angular.Module#controller registered
* controller} if passed as a string.
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
* If present, the controller will be published to scope under the `controllerAs` name.
* - `template` – `{string=|function()=}` – html template as a string or a function that
* - `template` – `{(string|Function)=}` – html template as a string or a function that
* returns an html template as a string which should be used by {@link
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
* This property takes precedence over `templateUrl`.
Expand All @@ -85,15 +85,15 @@ function $RouteProvider() {
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
* - `templateUrl` – `{(string|Function)=}` – path or function that returns a path to an html
* template that should be used by {@link ngRoute.directive:ngView ngView}.
*
* If `templateUrl` is a function, it will be called with the following parameters:
*
* - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route
*
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
* - `resolve` - `{Object.<string, Function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, the router
* will wait for them all to be resolved or one to be rejected before the controller is
* instantiated.
Expand All @@ -113,7 +113,7 @@ function $RouteProvider() {
* The map object is:
*
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
* - `factory` - `{string|Function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
* and the return value is treated as the dependency. If the result is a promise, it is
* resolved before its value is injected into the controller. Be aware that
Expand All @@ -123,7 +123,7 @@ function $RouteProvider() {
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
* the scope of the route. If omitted, defaults to `$resolve`.
*
* - `redirectTo` – `{(string|function())=}` – value to update
* - `redirectTo` – `{(string|Function)=}` – value to update
* {@link ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
Expand All @@ -134,14 +134,32 @@ function $RouteProvider() {
* - `{Object}` - current `$location.search()`
*
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
* to update `$location.url()`. If the function throws an error, no further processing will
* take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will
* be fired.
*
* Routes that specify `redirectTo` will not have their controllers, template functions
* or resolves called, the `$location` will be changed to the redirect url and route
* processing will stop. The exception to this is if the `redirectTo` is a function that
* returns `undefined`. In this case the route transition occurs as though there was no
* redirection.
*
* - `resolveRedirectTo` – `{Function=}` – a function that will (eventually) return the value
* to update {@link ng.$location $location} URL with and trigger route redirection. In
* contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the
* return value can be either a string or a promise that will be resolved to a string.
*
* Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets
* resolved to `undefined`), no redirection takes place and the route transition occurs as
* though there was no redirection.
*
* If the function throws an error or the returned promise gets rejected, no further
* processing will take place and the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired.
*
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
* route definition, will cause the latter to be ignored.
*
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
* or `$location.hash()` changes.
*
Expand Down Expand Up @@ -446,12 +464,14 @@ function $RouteProvider() {
* @name $route#$routeChangeError
* @eventType broadcast on root scope
* @description
* Broadcasted if any of the resolve promises are rejected.
* Broadcasted if a redirection function fails or any redirection or resolve promises are
* rejected.
*
* @param {Object} angularEvent Synthetic event object
* @param {Route} current Current route information.
* @param {Route} previous Previous route information.
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
* @param {Route} rejection The thrown error or the rejection reason of the promise. Usually
* the rejection reason is the error that caused the promise to get rejected.
*/

/**
Expand Down Expand Up @@ -592,44 +612,103 @@ function $RouteProvider() {
} else if (nextRoute || lastRoute) {
forceReload = false;
$route.current = nextRoute;
if (nextRoute) {
if (nextRoute.redirectTo) {
var url = $location.url();
var newUrl;
if (angular.isString(nextRoute.redirectTo)) {
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params))
.search(nextRoute.params)
.replace();
newUrl = $location.url();
} else {
newUrl = nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search());
$location.url(newUrl).replace();
}
if (angular.isDefined(newUrl) && url !== newUrl) {
return; //exit out and don't process current next value, wait for next location change from redirect
}
}
}

$q.when(nextRoute).
then(resolveLocals).
then(function(locals) {
// after route change
if (nextRoute === $route.current) {
if (nextRoute) {
nextRoute.locals = locals;
angular.copy(nextRoute.params, $routeParams);
}
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
}
}, function(error) {
var nextRoutePromise = $q.resolve(nextRoute);

nextRoutePromise.
then(getRedirectionData).
then(handlePossibleRedirection).
then(function(keepProcessingRoute) {
return keepProcessingRoute && nextRoutePromise.
then(resolveLocals).
then(function(locals) {
// after route change
if (nextRoute === $route.current) {
if (nextRoute) {
nextRoute.locals = locals;
angular.copy(nextRoute.params, $routeParams);
}
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
}
});
}).catch(function(error) {
if (nextRoute === $route.current) {
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
}
});
}
}

function getRedirectionData(route) {
var data = {
route: route,
hasRedirection: false
};

if (route) {
if (route.redirectTo) {
if (angular.isString(route.redirectTo)) {
data.path = interpolate(route.redirectTo, route.params);
data.search = route.params;
data.hasRedirection = true;
} else {
var oldPath = $location.path();
var oldSearch = $location.search();
var newUrl = route.redirectTo(route.pathParams, oldPath, oldSearch);

if (angular.isDefined(newUrl)) {
data.url = newUrl;
data.hasRedirection = true;
}
}
} else if (route.resolveRedirectTo) {
return $q.
resolve($injector.invoke(route.resolveRedirectTo)).
then(function(newUrl) {
if (angular.isDefined(newUrl)) {
data.url = newUrl;
data.hasRedirection = true;
}

return data;
});
}
}

return data;
}

function handlePossibleRedirection(data) {
var keepProcessingRoute = true;

if (data.route !== $route.current) {
keepProcessingRoute = false;
} else if (data.hasRedirection) {
var oldUrl = $location.url();
var newUrl = data.url;

if (newUrl) {
$location.
url(newUrl).
replace();
} else {
newUrl = $location.
path(data.path).
search(data.search).
replace().
url();
}

if (newUrl !== oldUrl) {
// Exit out and don't process current next value,
// wait for next location change from redirect
keepProcessingRoute = false;
}
}

return keepProcessingRoute;
}

function resolveLocals(route) {
if (route) {
var locals = angular.extend({}, route.resolve);
Expand All @@ -646,7 +725,6 @@ function $RouteProvider() {
}
}


function getTemplateFor(route) {
var template, templateUrl;
if (angular.isDefined(template = route.template)) {
Expand All @@ -665,7 +743,6 @@ function $RouteProvider() {
return template;
}


/**
* @returns {Object} the current active route, by matching it against the URL
*/
Expand Down
Loading