@@ -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