@@ -9,7 +9,7 @@ import { ng as angular } from "../../angular";
9
9
import { IAugmentedJQuery , ITimeoutService , IScope , IInterpolateService } from "angular" ;
10
10
11
11
import {
12
- Obj , extend , forEach , toJson , tail , isString , isObject , parse ,
12
+ Obj , extend , forEach , toJson , tail , isString , isObject , parse , noop ,
13
13
PathNode , StateOrName , StateService , TransitionService , State , UIRouter
14
14
} from "ui-router-core" ;
15
15
import { UIViewData } from "./viewDirective" ;
@@ -50,14 +50,14 @@ function getTypeInfo(el: IAugmentedJQuery): TypeInfo {
50
50
}
51
51
52
52
/** @hidden */
53
- function clickHook ( el : IAugmentedJQuery , $state : StateService , $timeout : ITimeoutService , type : TypeInfo , current : Function ) {
53
+ function clickHook ( el : IAugmentedJQuery , $state : StateService , $timeout : ITimeoutService , type : TypeInfo , getDef : ( ) => Def ) {
54
54
return function ( e : JQueryMouseEventObject ) {
55
- var button = e . which || e . button , target = current ( ) ;
55
+ var button = e . which || e . button , target = getDef ( ) ;
56
56
57
57
if ( ! ( button > 1 || e . ctrlKey || e . metaKey || e . shiftKey || el . attr ( 'target' ) ) ) {
58
58
// HACK: This is to allow ng-clicks to be processed before the transition is initiated:
59
59
var transition = $timeout ( function ( ) {
60
- $state . go ( target . state , target . params , target . options ) ;
60
+ $state . go ( target . uiState , target . uiStateParams , target . uiStateOpts ) ;
61
61
} ) ;
62
62
e . preventDefault ( ) ;
63
63
@@ -142,38 +142,42 @@ function defaultOpts(el: IAugmentedJQuery, $state: StateService) {
142
142
* @param {string } ui-sref 'stateName' can be any valid absolute or relative state
143
143
* @param {Object } ui-sref-opts options to pass to [[StateService.go]]
144
144
*/
145
- let uiSref = [ '$state' , '$timeout' ,
146
- function $StateRefDirective ( $state : StateService , $timeout : ITimeoutService ) {
145
+ let uiSref = [ '$uiRouter' , '$timeout' ,
146
+ function $StateRefDirective ( $uiRouter : UIRouter , $timeout : ITimeoutService ) {
147
+ let $state = $uiRouter . stateService ;
148
+
147
149
return {
148
150
restrict : 'A' ,
149
151
require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
150
152
link : function ( scope : IScope , element : IAugmentedJQuery , attrs : any , uiSrefActive : any ) {
151
153
var ref = parseStateRef ( attrs . uiSref , $state . current . name ) ;
152
- var def : Def = { state : ref . state , href : null , params : null , options : null } ;
154
+ var def = { uiState : ref . state } as Def ;
153
155
var type = getTypeInfo ( element ) ;
154
156
var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
155
157
var unlinkInfoFn : Function = null ;
156
158
var hookFn ;
157
159
158
- def . options = extend ( defaultOpts ( element , $state ) , attrs . uiSrefOpts ? scope . $eval ( attrs . uiSrefOpts ) : { } ) ;
160
+ def . uiStateOpts = extend ( defaultOpts ( element , $state ) , attrs . uiSrefOpts ? scope . $eval ( attrs . uiSrefOpts ) : { } ) ;
159
161
160
162
var update = function ( val ?: any ) {
161
- if ( val ) def . params = angular . copy ( val ) ;
162
- def . href = $state . href ( ref . state , def . params , def . options ) ;
163
+ if ( val ) def . uiStateParams = angular . copy ( val ) ;
164
+ def . href = $state . href ( ref . state , def . uiStateParams , def . uiStateOpts ) ;
163
165
164
166
if ( unlinkInfoFn ) unlinkInfoFn ( ) ;
165
- if ( active ) unlinkInfoFn = active . $$addStateInfo ( ref . state , def . params ) ;
167
+ if ( active ) unlinkInfoFn = active . $$addStateInfo ( ref . state , def . uiStateParams ) ;
166
168
if ( def . href !== null ) attrs . $set ( type . attr , def . href ) ;
167
169
} ;
168
170
169
171
if ( ref . paramExpr ) {
170
172
scope . $watch ( ref . paramExpr , function ( val ) {
171
- if ( val !== def . params ) update ( val ) ;
173
+ if ( val !== def . uiStateParams ) update ( val ) ;
172
174
} , true ) ;
173
- def . params = angular . copy ( scope . $eval ( ref . paramExpr ) ) ;
175
+ def . uiStateParams = angular . copy ( scope . $eval ( ref . paramExpr ) ) ;
174
176
}
175
177
update ( ) ;
176
178
179
+ scope . $on ( '$destroy' , < any > $uiRouter . stateRegistry . onStatesChanged ( ( ) => update ( ) ) ) ;
180
+
177
181
if ( ! type . clickable ) return ;
178
182
hookFn = clickHook ( element , $state , $timeout , type , function ( ) {
179
183
return def ;
@@ -187,52 +191,75 @@ let uiSref = ['$state', '$timeout',
187
191
} ] ;
188
192
189
193
/**
190
- * `ui-state`: A dynamic version of `ui-sref`
194
+ * `ui-state`: A dynamic version of the `ui-sref` directive
195
+ *
196
+ * Much like ui-sref, but it `$observe`s inputs and `$watch`es/evaluates values.
197
+ *
198
+ * The `ui-sref` directive takes a string literal, which is split into 1) state name and 2) parameter values expression.
199
+ * It does not `$observe` the input string and `$watch`es only the parameter value expression.
200
+ * Because of this, `ui-sref` is fairly lightweight, but does no deal well with with srefs that dynamically change.
201
+ *
202
+ *
203
+ * On the other hand, the `ui-state` directive is fully dynamic.
204
+ * It is useful for building dynamic links, such as data–driven navigation links.
205
+ *
206
+ * It consists of three attributes:
207
+ *
208
+ * - `ui-state="expr"`: The state to link to; the `expr` string is evaluated and `$watch`ed
209
+ * - `ui-state-params="expr"`: The state params to link to; the `expr` string is evaluated and `$watch`ed
210
+ * - `ui-state-opts="expr"`: The transition options for the link; the `expr` string is evaluated and `$watch`ed
191
211
*
192
- * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
193
- * params and override options.
212
+ * In angular 1.3 and above, a one time binding may be used if you know specific bindings will not change, i.e:
213
+ * `ui-params="::foo.params"`.
214
+ *
215
+ * Like `ui-sref`, this directive also works with `ui-sref-active` and `ui-sref-active-eq`.
194
216
*
195
217
* @example
196
218
* ```html
197
219
*
198
- * <li ng-repeat="nav in navlinks">
199
- * <a ui-state="nav.statename">{{nav.description}}</a>
220
+ * <li ng-repeat="nav in navlinks" ui-sref-active="active" >
221
+ * <a ui-state="nav.statename" ui-state-params="nav.params" >{{nav.description}}</a>
200
222
* </li>
201
223
* ```
202
- *
203
- * @param {string } ui-state 'stateName' can be any valid absolute or relative state
204
- * @param {Object } ui-state-params params to pass to [[StateService.href]]
205
- * @param {Object } ui-state-opts options to pass to [[StateService.go]]
206
224
*/
207
- let uiState = [ '$state' , '$timeout' ,
208
- function $StateRefDynamicDirective ( $state : StateService , $timeout : ITimeoutService ) {
225
+ let uiState = [ '$uiRouter' , '$timeout' ,
226
+ function $StateRefDynamicDirective ( $uiRouter : UIRouter , $timeout : ITimeoutService ) {
227
+ let $state = $uiRouter . stateService ;
228
+
209
229
return {
210
230
restrict : 'A' ,
211
231
require : [ '?^uiSrefActive' , '?^uiSrefActiveEq' ] ,
212
232
link : function ( scope : IScope , element : IAugmentedJQuery , attrs : any , uiSrefActive : any ) {
213
233
var type = getTypeInfo ( element ) ;
214
234
var active = uiSrefActive [ 1 ] || uiSrefActive [ 0 ] ;
215
- var group = [ attrs . uiState , attrs . uiStateParams || null , attrs . uiStateOpts || null ] ;
216
- var watch = '[' + group . map ( function ( val ) {
217
- return val || 'null' ;
218
- } ) . join ( ', ' ) + ']' ;
219
- var def : Def = { state : null , params : null , options : null , href : null } ;
235
+ var def = { } as Def ;
236
+ let inputAttrs = [ 'uiState' , 'uiStateParams' , 'uiStateOpts' ] ;
237
+ let watchDeregFns = inputAttrs . reduce ( ( acc , attr ) => ( acc [ attr ] = noop , acc ) , { } ) ;
220
238
var unlinkInfoFn : Function = null ;
221
239
var hookFn ;
222
240
223
- function runStateRefLink ( group : any [ ] ) {
224
- def . state = group [ 0 ] ;
225
- def . params = group [ 1 ] ;
226
- def . options = group [ 2 ] ;
227
- def . href = $state . href ( def . state , def . params , def . options ) ;
241
+ function update ( ) {
242
+ def . href = $state . href ( def . uiState , def . uiStateParams , def . uiStateOpts ) ;
228
243
229
244
if ( unlinkInfoFn ) unlinkInfoFn ( ) ;
230
- if ( active ) unlinkInfoFn = active . $$addStateInfo ( def . state , def . params ) ;
245
+ if ( active ) unlinkInfoFn = active . $$addStateInfo ( def . uiState , def . uiStateParams ) ;
231
246
if ( def . href ) attrs . $set ( type . attr , def . href ) ;
232
247
}
233
248
234
- scope . $watch ( watch , runStateRefLink , true ) ;
235
- runStateRefLink ( scope . $eval ( watch ) ) ;
249
+ inputAttrs . forEach ( ( field ) => {
250
+ def [ field ] = attrs [ field ] ? scope . $eval ( attrs [ field ] ) : null ;
251
+
252
+ attrs . $observe ( field , ( expr ) => {
253
+ watchDeregFns [ field ] ( ) ;
254
+ watchDeregFns [ field ] = scope . $watch ( expr , ( newval ) => {
255
+ def [ field ] = newval ;
256
+ update ( ) ;
257
+ } , true ) ;
258
+ } )
259
+ } ) ;
260
+
261
+ scope . $on ( '$destroy' , < any > $uiRouter . stateRegistry . onStatesChanged ( ( ) => update ( ) ) ) ;
262
+ update ( ) ;
236
263
237
264
if ( ! type . clickable ) return ;
238
265
hookFn = clickHook ( element , $state , $timeout , type , function ( ) {
@@ -456,7 +483,7 @@ let uiSrefActive = ['$state', '$stateParams', '$interpolate', '$transitions', '$
456
483
} ;
457
484
} ] ;
458
485
459
- interface Def { state : string ; href : string ; params : Obj ; options : any ;
486
+ interface Def { uiState : string ; href : string ; uiStateParams : Obj ; uiStateOpts : any ;
460
487
}
461
488
angular . module ( 'ui.router.state' )
462
489
. directive ( 'uiSref' , uiSref )
0 commit comments