1
1
/** @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" ;
4
3
import { UISref } from "./uiSref" ;
5
4
import { PathNode } from "../../path/node" ;
6
- import { TransitionService } from "../../transition/transitionService" ;
7
5
import { Transition } from "../../transition/transition" ;
8
6
import { TargetState } from "../../state/targetState" ;
9
- import { TreeChanges } from "../../transition/interface" ;
10
7
import { 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" ;
13
10
import { Param } from "../../params/param" ;
14
11
import { PathFactory } from "../../path/pathFactory" ;
12
+ import { Subscription , Observable } from "rxjs/Rx" ;
13
+
14
+ interface TransEvt { evt : string , trans : Transition }
15
15
16
16
/**
17
17
* uiSref status booleans
@@ -27,6 +27,84 @@ export interface SrefStatus {
27
27
exiting : boolean ;
28
28
}
29
29
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
+
30
108
/**
31
109
* A directive (which pairs with a [[UISref]]) and emits events when the UISref status changes.
32
110
*
@@ -40,110 +118,61 @@ export interface SrefStatus {
40
118
*/
41
119
@Directive ( { selector : '[uiSrefStatus],[uiSrefActive],[uiSrefActiveEq]' } )
42
120
export 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 */
46
122
@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 > ;
48
125
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 ;
55
130
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 ) ;
60
133
}
61
134
62
135
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 ) ) ;
67
168
}
68
169
69
170
ngOnDestroy ( ) {
70
- if ( this . _deregisterHook ) {
71
- this . _deregisterHook ( ) ;
72
- }
73
- this . _deregisterHook = null ;
171
+ if ( this . _subscription ) this . _subscription . unsubscribe ( ) ;
74
172
}
75
173
76
174
private _setStatus ( status : SrefStatus ) {
77
175
this . status = status ;
78
176
this . uiSrefStatus . emit ( status ) ;
79
177
}
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
- }
149
178
}
0 commit comments