11/** @module ng2_directives */ /** */
2- import { Directive , Output , EventEmitter , ContentChild } from "@angular/core" ;
3- import { StateService } from "../../state/stateService" ;
2+ import { Directive , Output , EventEmitter , ContentChildren , QueryList , Inject } from "@angular/core" ;
43import { UISref } from "./uiSref" ;
54import { PathNode } from "../../path/node" ;
6- import { TransitionService } from "../../transition/transitionService" ;
75import { Transition } from "../../transition/transition" ;
86import { TargetState } from "../../state/targetState" ;
9- import { TreeChanges } from "../../transition/interface" ;
107import { State } from "../../state/stateObject" ;
11- import { anyTrueR , tail , unnestR } from "../../common/common" ;
12- import { Globals } from "../../globals" ;
8+ import { anyTrueR , tail , unnestR , Predicate } from "../../common/common" ;
9+ import { Globals , UIRouterGlobals } from "../../globals" ;
1310import { Param } from "../../params/param" ;
1411import { PathFactory } from "../../path/pathFactory" ;
12+ import { Subscription , Observable } from "rxjs/Rx" ;
13+
14+ interface TransEvt { evt : string , trans : Transition }
1515
1616/**
1717 * uiSref status booleans
@@ -27,6 +27,84 @@ export interface SrefStatus {
2727 exiting : boolean ;
2828}
2929
30+ const inactiveStatus : SrefStatus = {
31+ active : false ,
32+ exact : false ,
33+ entering : false ,
34+ exiting : false
35+ } ;
36+
37+ /**
38+ * Returns a Predicate<PathNode[]>
39+ *
40+ * The predicate returns true when the target state (and param values)
41+ * match the (tail of) the path, and the path's param values
42+ */
43+ const pathMatches = ( target : TargetState ) : Predicate < PathNode [ ] > => {
44+ let state : State = target . $state ( ) ;
45+ let targetParamVals = target . params ( ) ;
46+ let targetPath : PathNode [ ] = PathFactory . buildPath ( target ) ;
47+ let paramSchema : Param [ ] = targetPath . map ( node => node . paramSchema )
48+ . reduce ( unnestR , [ ] )
49+ . filter ( ( param : Param ) => targetParamVals . hasOwnProperty ( param . id ) ) ;
50+
51+ return ( path : PathNode [ ] ) => {
52+ let tailNode = tail ( path ) ;
53+ if ( ! tailNode || tailNode . state !== state ) return false ;
54+ var paramValues = PathFactory . paramValues ( path ) ;
55+ return Param . equals ( paramSchema , paramValues , targetParamVals ) ;
56+ } ;
57+ } ;
58+
59+ /**
60+ * Given basePath: [a, b], appendPath: [c, d]),
61+ * Expands the path to [c], [c, d]
62+ * Then appends each to [a,b,] and returns: [a, b, c], [a, b, c, d]
63+ */
64+ function spreadToSubPaths ( basePath : PathNode [ ] , appendPath : PathNode [ ] ) : PathNode [ ] [ ] {
65+ return appendPath . map ( node => basePath . concat ( PathFactory . subPath ( appendPath , n => n . state === node . state ) ) ) ;
66+ }
67+
68+ /**
69+ * Given a TransEvt (Transition event: started, success, error)
70+ * and a UISref Target State, return a SrefStatus object
71+ * which represents the current status of that Sref:
72+ * active, activeEq (exact match), entering, exiting
73+ */
74+ function getSrefStatus ( event : TransEvt , srefTarget : TargetState ) : SrefStatus {
75+ const pathMatchesTarget = pathMatches ( srefTarget ) ;
76+ const tc = event . trans . treeChanges ( ) ;
77+
78+ let isStartEvent = event . evt === 'start' ;
79+ let isSuccessEvent = event . evt === 'success' ;
80+ let activePath : PathNode [ ] = isSuccessEvent ? tc . to : tc . from ;
81+
82+ const isActive = ( ) =>
83+ spreadToSubPaths ( [ ] , activePath )
84+ . map ( pathMatchesTarget )
85+ . reduce ( anyTrueR , false ) ;
86+
87+ const isExact = ( ) =>
88+ pathMatchesTarget ( activePath ) ;
89+
90+ const isEntering = ( ) =>
91+ spreadToSubPaths ( tc . retained , tc . entering )
92+ . map ( pathMatchesTarget )
93+ . reduce ( anyTrueR , false ) ;
94+
95+ const isExiting = ( ) =>
96+ spreadToSubPaths ( tc . retained , tc . exiting )
97+ . map ( pathMatchesTarget )
98+ . reduce ( anyTrueR , false ) ;
99+
100+ return {
101+ active : isActive ( ) ,
102+ exact : isExact ( ) ,
103+ entering : isStartEvent ? isEntering ( ) : false ,
104+ exiting : isStartEvent ? isExiting ( ) : false ,
105+ } as SrefStatus ;
106+ }
107+
30108/**
31109 * A directive (which pairs with a [[UISref]]) and emits events when the UISref status changes.
32110 *
@@ -40,110 +118,61 @@ export interface SrefStatus {
40118 */
41119@Directive ( { selector : '[uiSrefStatus],[uiSrefActive],[uiSrefActiveEq]' } )
42120export class UISrefStatus {
43- private _deregisterHook : Function ;
44-
45- // current statuses of the state/params the uiSref directive is linking to
121+ /** current statuses of the state/params the uiSref directive is linking to */
46122 @Output ( "uiSrefStatus" ) uiSrefStatus = new EventEmitter < SrefStatus > ( false ) ;
47- @ContentChild ( UISref ) sref : UISref ;
123+ /** Monitor all child components for UISref(s) */
124+ @ContentChildren ( UISref , { descendants : true } ) srefs : QueryList < UISref > ;
48125
49- status : SrefStatus = {
50- active : false ,
51- exact : false ,
52- entering : false ,
53- exiting : false
54- } ;
126+ /** The current status */
127+ status : SrefStatus ;
128+
129+ private _subscription : Subscription ;
55130
56- constructor ( transitionService : TransitionService ,
57- private _globals : Globals ,
58- private _stateService : StateService ) {
59- this . _deregisterHook = transitionService . onStart ( { } , $transition$ => this . processTransition ( $transition$ ) ) ;
131+ constructor ( @Inject ( Globals ) private _globals : UIRouterGlobals ) {
132+ this . status = Object . assign ( { } , inactiveStatus ) ;
60133 }
61134
62135 ngAfterContentInit ( ) {
63- let lastTrans = this . _globals . transitionHistory . peekTail ( ) ;
64- if ( lastTrans != null ) {
65- this . processTransition ( lastTrans ) ;
66- }
136+ // Map each transition start event to a stream of:
137+ // start -> (success|error)
138+ let transEvents$ : Observable < TransEvt > = this . _globals . start$ . switchMap ( ( trans : Transition ) => {
139+ const event = ( evt : string ) => ( { evt, trans} as TransEvt ) ;
140+
141+ let transStart$ = Observable . of ( event ( "start" ) ) ;
142+ let transResult = trans . promise . then ( ( ) => event ( "success" ) , ( ) => event ( "error" ) ) ;
143+ let transFinish$ = Observable . fromPromise ( transResult ) ;
144+
145+ return transStart$ . concat ( transFinish$ ) ;
146+ } ) ;
147+
148+ // Watch the children UISref components and get their target states
149+ let srefs$ : Observable < UISref [ ] > = Observable . of ( this . srefs . toArray ( ) ) . concat ( this . srefs . changes ) ;
150+ let targetStates$ : Observable < TargetState [ ] > =
151+ srefs$ . switchMap ( ( srefs : UISref [ ] ) =>
152+ Observable . combineLatest < TargetState [ ] > ( srefs . map ( sref => sref . targetState$ ) ) ) ;
153+
154+ // Calculate the status of each UISref based on the transition event.
155+ // Reduce the statuses (if multiple) by or-ing each flag.
156+ this . _subscription = transEvents$ . mergeMap ( ( evt : TransEvt ) => {
157+ return targetStates$ . map ( ( targets : TargetState [ ] ) => {
158+ let statuses : SrefStatus [ ] = targets . map ( target => getSrefStatus ( evt , target ) ) ;
159+
160+ return statuses . reduce ( ( acc : SrefStatus , val : SrefStatus ) => ( {
161+ active : acc . active || val . active ,
162+ exact : acc . active || val . active ,
163+ entering : acc . active || val . active ,
164+ exiting : acc . active || val . active ,
165+ } ) )
166+ } )
167+ } ) . subscribe ( this . _setStatus . bind ( this ) ) ;
67168 }
68169
69170 ngOnDestroy ( ) {
70- if ( this . _deregisterHook ) {
71- this . _deregisterHook ( ) ;
72- }
73- this . _deregisterHook = null ;
171+ if ( this . _subscription ) this . _subscription . unsubscribe ( ) ;
74172 }
75173
76174 private _setStatus ( status : SrefStatus ) {
77175 this . status = status ;
78176 this . uiSrefStatus . emit ( status ) ;
79177 }
80-
81- private processTransition ( $transition$ : Transition ) {
82- let sref = this . sref ;
83-
84- let status : SrefStatus = < any > {
85- active : false ,
86- exact : false ,
87- entering : false ,
88- exiting : false
89- } ;
90-
91- let srefTarget : TargetState = this . _stateService . target ( sref . state , sref . params , sref . getOptions ( ) ) ;
92- if ( ! srefTarget . exists ( ) ) {
93- return this . _setStatus ( status ) ;
94- }
95-
96-
97- /**
98- * Returns a Predicate<PathNode[]> that returns true when the target state (and any param values)
99- * match the (tail of) the path, and the path's param values
100- */
101- const pathMatches = ( target : TargetState ) => {
102- let state : State = target . $state ( ) ;
103- let targetParamVals = target . params ( ) ;
104- let targetPath : PathNode [ ] = PathFactory . buildPath ( target ) ;
105- let paramSchema : Param [ ] = targetPath . map ( node => node . paramSchema )
106- . reduce ( unnestR , [ ] )
107- . filter ( ( param : Param ) => targetParamVals . hasOwnProperty ( param . id ) ) ;
108-
109- return ( path : PathNode [ ] ) => {
110- let tailNode = tail ( path ) ;
111- if ( ! tailNode || tailNode . state !== state ) return false ;
112- var paramValues = PathFactory . paramValues ( path ) ;
113- return Param . equals ( paramSchema , paramValues , targetParamVals ) ;
114- } ;
115- } ;
116-
117- const isTarget = pathMatches ( srefTarget ) ;
118-
119- /**
120- * Given path: [c, d] appendTo: [a, b]),
121- * Expands the path to [c], [c, d]
122- * Then appends each to [a,b,] and returns: [a, b, c], [a, b, c, d]
123- */
124- function spreadToSubPaths ( path : PathNode [ ] , appendTo : PathNode [ ] = [ ] ) : PathNode [ ] [ ] {
125- return path . map ( node => appendTo . concat ( PathFactory . subPath ( path , n => n . state === node . state ) ) ) ;
126- }
127-
128- let tc : TreeChanges = $transition$ . treeChanges ( ) ;
129- status . active = spreadToSubPaths ( tc . from ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
130- status . exact = isTarget ( tc . from ) ;
131- status . entering = spreadToSubPaths ( tc . entering , tc . retained ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
132- status . exiting = spreadToSubPaths ( tc . exiting , tc . retained ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
133-
134- if ( $transition$ . isActive ( ) ) {
135- this . _setStatus ( status ) ;
136- }
137-
138- let update = ( currentPath : PathNode [ ] ) => ( ) => {
139- if ( this . _deregisterHook == null ) return ; // destroyed
140- if ( ! $transition$ . isActive ( ) ) return ; // superseded
141- status . active = spreadToSubPaths ( currentPath ) . map ( isTarget ) . reduce ( anyTrueR , false ) ;
142- status . exact = isTarget ( currentPath ) ;
143- status . entering = status . exiting = false ;
144- this . _setStatus ( status ) ;
145- } ;
146-
147- $transition$ . promise . then ( update ( tc . to ) , update ( tc . from ) ) ;
148- }
149178}
0 commit comments