@@ -553,6 +553,71 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
553553 }
554554 }
555555
556+ // Handles the case where a state which is the target of a transition is not found, and the user
557+ // can optionally retry or defer the transition
558+ function handleRedirect ( redirect , state , params , options ) {
559+ /**
560+ * @ngdoc event
561+ * @name ui.router.state.$state#$stateNotFound
562+ * @eventOf ui.router.state.$state
563+ * @eventType broadcast on root scope
564+ * @description
565+ * Fired when a requested state **cannot be found** using the provided state name during transition.
566+ * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
567+ * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
568+ * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
569+ * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
570+ *
571+ * @param {Object } event Event object.
572+ * @param {Object } unfoundState Unfound State information. Contains: `to, toParams, options` properties.
573+ * @param {State } fromState Current state object.
574+ * @param {Object } fromParams Current state params.
575+ *
576+ * @example
577+ *
578+ * <pre>
579+ * // somewhere, assume lazy.state has not been defined
580+ * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
581+ *
582+ * // somewhere else
583+ * $scope.$on('$stateNotFound',
584+ * function(event, unfoundState, fromState, fromParams){
585+ * console.log(unfoundState.to); // "lazy.state"
586+ * console.log(unfoundState.toParams); // {a:1, b:2}
587+ * console.log(unfoundState.options); // {inherit:false} + default options
588+ * })
589+ * </pre>
590+ */
591+ var evt = $rootScope . $broadcast ( '$stateNotFound' , redirect , state , params ) ;
592+
593+ if ( evt . defaultPrevented ) {
594+ syncUrl ( ) ;
595+ return TransitionAborted ;
596+ }
597+
598+ if ( ! evt . retry ) {
599+ return null ;
600+ }
601+
602+ // Allow the handler to return a promise to defer state lookup retry
603+ if ( options . $retry ) {
604+ syncUrl ( ) ;
605+ return TransitionFailed ;
606+ }
607+ var retryTransition = $state . transition = $q . when ( evt . retry ) ;
608+
609+ retryTransition . then ( function ( ) {
610+ if ( retryTransition !== $state . transition ) return TransitionSuperseded ;
611+ redirect . options . $retry = true ;
612+ return $state . transitionTo ( redirect . to , redirect . toParams , redirect . options ) ;
613+ } , function ( ) {
614+ return TransitionAborted ;
615+ } ) ;
616+ syncUrl ( ) ;
617+
618+ return retryTransition ;
619+ }
620+
556621 root . locals = { resolve : null , globals : { $stateParams : { } } } ;
557622 $state = {
558623 params : { } ,
@@ -710,63 +775,11 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
710775 var evt , toState = findState ( to , options . relative ) ;
711776
712777 if ( ! isDefined ( toState ) ) {
713- // Broadcast not found event and abort the transition if prevented
714778 var redirect = { to : to , toParams : toParams , options : options } ;
779+ var redirectResult = handleRedirect ( redirect , from . self , fromParams , options ) ;
715780
716- /**
717- * @ngdoc event
718- * @name ui.router.state.$state#$stateNotFound
719- * @eventOf ui.router.state.$state
720- * @eventType broadcast on root scope
721- * @description
722- * Fired when a requested state **cannot be found** using the provided state name during transition.
723- * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
724- * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
725- * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
726- * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
727- *
728- * @param {Object } event Event object.
729- * @param {Object } unfoundState Unfound State information. Contains: `to, toParams, options` properties.
730- * @param {State } fromState Current state object.
731- * @param {Object } fromParams Current state params.
732- *
733- * @example
734- *
735- * <pre>
736- * // somewhere, assume lazy.state has not been defined
737- * $state.go("lazy.state", {a:1, b:2}, {inherit:false});
738- *
739- * // somewhere else
740- * $scope.$on('$stateNotFound',
741- * function(event, unfoundState, fromState, fromParams){
742- * console.log(unfoundState.to); // "lazy.state"
743- * console.log(unfoundState.toParams); // {a:1, b:2}
744- * console.log(unfoundState.options); // {inherit:false} + default options
745- * })
746- * </pre>
747- */
748- evt = $rootScope . $broadcast ( '$stateNotFound' , redirect , from . self , fromParams ) ;
749- if ( evt . defaultPrevented ) {
750- syncUrl ( ) ;
751- return TransitionAborted ;
752- }
753-
754- // Allow the handler to return a promise to defer state lookup retry
755- if ( evt . retry ) {
756- if ( options . $retry ) {
757- syncUrl ( ) ;
758- return TransitionFailed ;
759- }
760- var retryTransition = $state . transition = $q . when ( evt . retry ) ;
761- retryTransition . then ( function ( ) {
762- if ( retryTransition !== $state . transition ) return TransitionSuperseded ;
763- redirect . options . $retry = true ;
764- return $state . transitionTo ( redirect . to , redirect . toParams , redirect . options ) ;
765- } , function ( ) {
766- return TransitionAborted ;
767- } ) ;
768- syncUrl ( ) ;
769- return retryTransition ;
781+ if ( redirectResult ) {
782+ return redirectResult ;
770783 }
771784
772785 // Always retry once if the $stateNotFound was not prevented
@@ -775,6 +788,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
775788 toParams = redirect . toParams ;
776789 options = redirect . options ;
777790 toState = findState ( to , options . relative ) ;
791+
778792 if ( ! isDefined ( toState ) ) {
779793 if ( options . relative ) throw new Error ( "Could not resolve '" + to + "' from state '" + options . relative + "'" ) ;
780794 throw new Error ( "No such state '" + to + "'" ) ;
@@ -787,19 +801,22 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
787801 var toPath = to . path ;
788802
789803 // Starting from the root of the path, keep all levels that haven't changed
790- var keep , state , locals = root . locals , toLocals = [ ] ;
791- for ( keep = 0 , state = toPath [ keep ] ;
792- state && state === fromPath [ keep ] && equalForKeys ( toParams , fromParams , state . ownParams ) && ! options . reload ;
793- keep ++ , state = toPath [ keep ] ) {
794- locals = toLocals [ keep ] = state . locals ;
804+ var keep = 0 , state = toPath [ keep ] , locals = root . locals , toLocals = [ ] ;
805+
806+ if ( ! options . reload ) {
807+ while ( state && state === fromPath [ keep ] && equalForKeys ( toParams , fromParams , state . ownParams ) ) {
808+ locals = toLocals [ keep ] = state . locals ;
809+ keep ++ ;
810+ state = toPath [ keep ] ;
811+ }
795812 }
796813
797814 // If we're going to the same state and all locals are kept, we've got nothing to do.
798815 // But clear 'transition', as we still want to cancel any other pending transitions.
799816 // TODO: We may not want to bump 'transition' if we're called from a location change that we've initiated ourselves,
800817 // because we might accidentally abort a legitimate transition initiated from code?
801818 if ( shouldTriggerReload ( to , from , locals , options ) ) {
802- if ( to . self . reloadOnSearch !== false )
819+ if ( to . self . reloadOnSearch !== false )
803820 syncUrl ( ) ;
804821 $state . transition = null ;
805822 return $q . when ( $state . current ) ;
@@ -837,8 +854,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
837854 * })
838855 * </pre>
839856 */
840- evt = $rootScope . $broadcast ( '$stateChangeStart' , to . self , toParams , from . self , fromParams ) ;
841- if ( evt . defaultPrevented ) {
857+ if ( $rootScope . $broadcast ( '$stateChangeStart' , to . self , toParams , from . self , fromParams ) . defaultPrevented ) {
842858 syncUrl ( ) ;
843859 return TransitionPrevented ;
844860 }
@@ -852,9 +868,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
852868 // empty and gets filled asynchronously. We need to keep track of the promise for the
853869 // (fully resolved) current locals, and pass this down the chain.
854870 var resolved = $q . when ( locals ) ;
855- for ( var l = keep ; l < toPath . length ; l ++ , state = toPath [ l ] ) {
871+ for ( var l = keep ; l < toPath . length ; l ++ , state = toPath [ l ] ) {
856872 locals = toLocals [ l ] = inherit ( locals ) ;
857- resolved = resolveState ( state , toParams , state === to , resolved , locals ) ;
873+ resolved = resolveState ( state , toParams , state === to , resolved , locals ) ;
858874 }
859875
860876 // Once everything is resolved, we are ready to perform the actual transition
@@ -867,7 +883,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
867883 if ( $state . transition !== transition ) return TransitionSuperseded ;
868884
869885 // Exit 'from' states not kept
870- for ( l = fromPath . length - 1 ; l >= keep ; l -- ) {
886+ for ( l = fromPath . length - 1 ; l >= keep ; l -- ) {
871887 exiting = fromPath [ l ] ;
872888 if ( exiting . self . onExit ) {
873889 $injector . invoke ( exiting . self . onExit , exiting . self , exiting . locals . globals ) ;
@@ -876,7 +892,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
876892 }
877893
878894 // Enter 'to' states not kept
879- for ( l = keep ; l < toPath . length ; l ++ ) {
895+ for ( l = keep ; l < toPath . length ; l ++ ) {
880896 entering = toPath [ l ] ;
881897 entering . locals = toLocals [ l ] ;
882898 if ( entering . self . onEnter ) {
@@ -1197,7 +1213,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
11971213 }
11981214
11991215 function shouldTriggerReload ( to , from , locals , options ) {
1200- if ( to === from && ( ( locals === from . locals && ! options . reload ) || ( to . self . reloadOnSearch === false ) ) ) {
1216+ if ( to === from && ( ( locals === from . locals && ! options . reload ) || ( to . self . reloadOnSearch === false ) ) ) {
12011217 return true ;
12021218 }
12031219 }
0 commit comments