@@ -70,12 +70,12 @@ function $RouteProvider() {
70
70
*
71
71
* Object properties:
72
72
*
73
- * - `controller` – `{(string|function( )=}` – Controller fn that should be associated with
73
+ * - `controller` – `{(string|Function )=}` – Controller fn that should be associated with
74
74
* newly created scope or the name of a {@link angular.Module#controller registered
75
75
* controller} if passed as a string.
76
76
* - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
77
77
* 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
79
79
* returns an html template as a string which should be used by {@link
80
80
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
81
81
* This property takes precedence over `templateUrl`.
@@ -85,15 +85,15 @@ function $RouteProvider() {
85
85
* - `{Array.<Object>}` - route parameters extracted from the current
86
86
* `$location.path()` by applying the current route
87
87
*
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
89
89
* template that should be used by {@link ngRoute.directive:ngView ngView}.
90
90
*
91
91
* If `templateUrl` is a function, it will be called with the following parameters:
92
92
*
93
93
* - `{Array.<Object>}` - route parameters extracted from the current
94
94
* `$location.path()` by applying the current route
95
95
*
96
- * - `resolve` - `{Object.<string, function >=}` - An optional map of dependencies which should
96
+ * - `resolve` - `{Object.<string, Function >=}` - An optional map of dependencies which should
97
97
* be injected into the controller. If any of these dependencies are promises, the router
98
98
* will wait for them all to be resolved or one to be rejected before the controller is
99
99
* instantiated.
@@ -113,7 +113,7 @@ function $RouteProvider() {
113
113
* The map object is:
114
114
*
115
115
* - `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.
117
117
* Otherwise if function, then it is {@link auto.$injector#invoke injected}
118
118
* and the return value is treated as the dependency. If the result is a promise, it is
119
119
* resolved before its value is injected into the controller. Be aware that
@@ -123,7 +123,7 @@ function $RouteProvider() {
123
123
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
124
124
* the scope of the route. If omitted, defaults to `$resolve`.
125
125
*
126
- * - `redirectTo` – `{(string|function() )=}` – value to update
126
+ * - `redirectTo` – `{(string|Function )=}` – value to update
127
127
* {@link ng.$location $location} path with and trigger route redirection.
128
128
*
129
129
* If `redirectTo` is a function, it will be called with the following parameters:
@@ -134,14 +134,32 @@ function $RouteProvider() {
134
134
* - `{Object}` - current `$location.search()`
135
135
*
136
136
* 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.
138
140
*
139
141
* Routes that specify `redirectTo` will not have their controllers, template functions
140
142
* or resolves called, the `$location` will be changed to the redirect url and route
141
143
* processing will stop. The exception to this is if the `redirectTo` is a function that
142
144
* returns `undefined`. In this case the route transition occurs as though there was no
143
145
* redirection.
144
146
*
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
+ *
145
163
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
146
164
* or `$location.hash()` changes.
147
165
*
@@ -446,12 +464,14 @@ function $RouteProvider() {
446
464
* @name $route#$routeChangeError
447
465
* @eventType broadcast on root scope
448
466
* @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.
450
469
*
451
470
* @param {Object } angularEvent Synthetic event object
452
471
* @param {Route } current Current route information.
453
472
* @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.
455
475
*/
456
476
457
477
/**
@@ -592,44 +612,103 @@ function $RouteProvider() {
592
612
} else if ( nextRoute || lastRoute ) {
593
613
forceReload = false ;
594
614
$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
- }
613
615
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 ) {
626
635
if ( nextRoute === $route . current ) {
627
636
$rootScope . $broadcast ( '$routeChangeError' , nextRoute , lastRoute , error ) ;
628
637
}
629
638
} ) ;
630
639
}
631
640
}
632
641
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
+
633
712
function resolveLocals ( route ) {
634
713
if ( route ) {
635
714
var locals = angular . extend ( { } , route . resolve ) ;
@@ -646,7 +725,6 @@ function $RouteProvider() {
646
725
}
647
726
}
648
727
649
-
650
728
function getTemplateFor ( route ) {
651
729
var template , templateUrl ;
652
730
if ( angular . isDefined ( template = route . template ) ) {
@@ -665,7 +743,6 @@ function $RouteProvider() {
665
743
return template ;
666
744
}
667
745
668
-
669
746
/**
670
747
* @returns {Object } the current active route, by matching it against the URL
671
748
*/
0 commit comments