@@ -76,12 +76,12 @@ function $RouteProvider() {
76
76
*
77
77
* Object properties:
78
78
*
79
- * - `controller` – `{(string|function( )=}` – Controller fn that should be associated with
79
+ * - `controller` – `{(string|Function )=}` – Controller fn that should be associated with
80
80
* newly created scope or the name of a {@link angular.Module#controller registered
81
81
* controller} if passed as a string.
82
82
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
83
83
* 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
85
85
* returns an html template as a string which should be used by {@link
86
86
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
87
87
* This property takes precedence over `templateUrl`.
@@ -91,15 +91,15 @@ function $RouteProvider() {
91
91
* - `{Array.<Object>}` - route parameters extracted from the current
92
92
* `$location.path()` by applying the current route
93
93
*
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
95
95
* template that should be used by {@link ngRoute.directive:ngView ngView}.
96
96
*
97
97
* If `templateUrl` is a function, it will be called with the following parameters:
98
98
*
99
99
* - `{Array.<Object>}` - route parameters extracted from the current
100
100
* `$location.path()` by applying the current route
101
101
*
102
- * - `resolve` - `{Object.<string, function >=}` - An optional map of dependencies which should
102
+ * - `resolve` - `{Object.<string, Function >=}` - An optional map of dependencies which should
103
103
* be injected into the controller. If any of these dependencies are promises, the router
104
104
* will wait for them all to be resolved or one to be rejected before the controller is
105
105
* instantiated.
@@ -119,7 +119,7 @@ function $RouteProvider() {
119
119
* The map object is:
120
120
*
121
121
* - `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.
123
123
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
124
124
* and the return value is treated as the dependency. If the result is a promise, it is
125
125
* resolved before its value is injected into the controller. Be aware that
@@ -129,7 +129,7 @@ function $RouteProvider() {
129
129
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
130
130
* the scope of the route. If omitted, defaults to `$resolve`.
131
131
*
132
- * - `redirectTo` – `{(string|function() )=}` – value to update
132
+ * - `redirectTo` – `{(string|Function )=}` – value to update
133
133
* {@link ng.$location $location} path with and trigger route redirection.
134
134
*
135
135
* If `redirectTo` is a function, it will be called with the following parameters:
@@ -140,14 +140,32 @@ function $RouteProvider() {
140
140
* - `{Object}` - current `$location.search()`
141
141
*
142
142
* 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.
144
146
*
145
147
* Routes that specify `redirectTo` will not have their controllers, template functions
146
148
* or resolves called, the `$location` will be changed to the redirect url and route
147
149
* processing will stop. The exception to this is if the `redirectTo` is a function that
148
150
* returns `undefined`. In this case the route transition occurs as though there was no
149
151
* redirection.
150
152
*
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
+ *
151
169
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
152
170
* or `$location.hash()` changes.
153
171
*
@@ -452,12 +470,14 @@ function $RouteProvider() {
452
470
* @name $route#$routeChangeError
453
471
* @eventType broadcast on root scope
454
472
* @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.
456
475
*
457
476
* @param {Object } angularEvent Synthetic event object
458
477
* @param {Route } current Current route information.
459
478
* @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.
461
481
*/
462
482
463
483
/**
@@ -598,44 +618,103 @@ function $RouteProvider() {
598
618
} else if ( nextRoute || lastRoute ) {
599
619
forceReload = false ;
600
620
$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
- }
619
621
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 ) {
632
641
if ( nextRoute === $route . current ) {
633
642
$rootScope . $broadcast ( '$routeChangeError' , nextRoute , lastRoute , error ) ;
634
643
}
635
644
} ) ;
636
645
}
637
646
}
638
647
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
+
639
718
function resolveLocals ( route ) {
640
719
if ( route ) {
641
720
var locals = angular . extend ( { } , route . resolve ) ;
@@ -652,7 +731,6 @@ function $RouteProvider() {
652
731
}
653
732
}
654
733
655
-
656
734
function getTemplateFor ( route ) {
657
735
var template , templateUrl ;
658
736
if ( angular . isDefined ( template = route . template ) ) {
@@ -671,7 +749,6 @@ function $RouteProvider() {
671
749
return template ;
672
750
}
673
751
674
-
675
752
/**
676
753
* @returns {Object } the current active route, by matching it against the URL
677
754
*/
0 commit comments