@@ -24,7 +24,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
24
24
} ) ;
25
25
26
26
var name = state . name ;
27
- if ( ! isString ( name ) ) throw new Error ( "State must have a name" ) ;
27
+ if ( ! isString ( name ) || name . indexOf ( '@' ) >= 0 ) throw new Error ( "State must have a valid name" ) ;
28
28
if ( states [ name ] ) throw new Error ( "State '" + name + "'' is already defined" ) ;
29
29
30
30
// Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
@@ -59,12 +59,21 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
59
59
// Keep track of the closest ancestor state that has a URL (i.e. is navigable)
60
60
state . navigable = state . url ? state : parent ? parent . navigable : null ;
61
61
62
- // Figure out the parameters for this state and ensure they're a super-set of parent's parameters
63
- var params = state . params = url ? url . parameters ( ) : state . parent . params ;
62
+ // Derive parameters for this state and ensure they're a super-set of parent's parameters
63
+ var params = state . params ;
64
+ if ( params ) {
65
+ if ( ! isArray ( params ) ) throw new Error ( "Invalid params in state '" + state + "'" ) ;
66
+ if ( url ) throw new Error ( "Both params and url specicified in state '" + state + "'" ) ;
67
+ } else {
68
+ params = state . params = url ? url . parameters ( ) : state . parent . params ;
69
+ }
70
+
64
71
var paramNames = { } ; forEach ( params , function ( p ) { paramNames [ p ] = true ; } ) ;
65
72
if ( parent ) {
66
73
forEach ( parent . params , function ( p ) {
67
- if ( ! paramNames [ p ] ) throw new Error ( "State '" + name + "' does not define parameter '" + p + "'" ) ;
74
+ if ( ! paramNames [ p ] ) {
75
+ throw new Error ( "Missing required parameter '" + p + "' in state '" + name + "'" ) ;
76
+ }
68
77
paramNames [ p ] = false ;
69
78
} ) ;
70
79
@@ -148,6 +157,9 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
148
157
}
149
158
} ;
150
159
160
+ var TransitionSuperseded = $q . reject ( new Error ( 'transition superseded' ) ) ;
161
+ var TransitionPrevented = $q . reject ( new Error ( 'transition prevented' ) ) ;
162
+
151
163
function transitionTo ( to , toParams , updateLocation ) {
152
164
if ( ! isDefined ( updateLocation ) ) updateLocation = true ;
153
165
@@ -171,8 +183,17 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
171
183
return $q . when ( $state . current ) ;
172
184
}
173
185
174
- // TODO: should we be passing from and to $stateParams as well?
175
- $rootScope . $broadcast ( '$stateChangeStart' , to . self , from . self ) ;
186
+ // Normalize parameters before we pass them to event handlers etc.
187
+ var normalizedToParams = { } ;
188
+ forEach ( to . params , function ( name ) {
189
+ var value = toParams [ name ] ;
190
+ normalizedToParams [ name ] = ( value != null ) ? String ( value ) : null ;
191
+ } ) ;
192
+ toParams = normalizedToParams ;
193
+
194
+ // Broadcast start event and cancel the transition if requested
195
+ if ( $rootScope . $broadcast ( '$stateChangeStart' , to . self , toParams , from . self , fromParams )
196
+ . defaultPrevented ) return TransitionPrevented ;
176
197
177
198
// Resolve locals for the remaining states, but don't update any global state just
178
199
// yet -- if anything fails to resolve the current state needs to remain untouched.
@@ -184,7 +205,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
184
205
var resolved = $q . when ( locals ) ;
185
206
for ( var l = keep ; l < toPath . length ; l ++ , state = toPath [ l ] ) {
186
207
locals = toLocals [ l ] = inherit ( locals ) ;
187
- resolved = resolveState ( state , toParams , resolved , locals ) ;
208
+ resolved = resolveState ( state , toParams , state === to , resolved , locals ) ;
188
209
}
189
210
190
211
// Once everything is resolved, we are ready to perform the actual transition
@@ -194,7 +215,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
194
215
var transition = $state . transition = resolved . then ( function ( ) {
195
216
var l , entering , exiting ;
196
217
197
- if ( $state . transition !== transition ) return $q . reject ( new Error ( 'transition superseded' ) ) ;
218
+ if ( $state . transition !== transition ) return TransitionSuperseded ;
198
219
199
220
// Exit 'from' states not kept
200
221
for ( l = fromPath . length - 1 ; l >= keep ; l -- ) {
@@ -218,7 +239,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
218
239
// Update globals in $state
219
240
$state . $current = to ;
220
241
$state . current = to . self ;
221
- $state . params = locals . globals . $stateParams ; // these are normalized, unlike toParams
242
+ $state . params = toParams
222
243
copy ( $state . params , $stateParams ) ;
223
244
$state . transition = null ;
224
245
@@ -228,36 +249,39 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
228
249
$location . url ( toNav . url . format ( toNav . locals . globals . $stateParams ) ) ;
229
250
}
230
251
231
- $rootScope . $broadcast ( '$stateChangeSuccess' , to . self , from . self ) ;
252
+ $rootScope . $broadcast ( '$stateChangeSuccess' , to . self , toParams , from . self , fromParams ) ;
232
253
233
254
return $state . current ;
234
255
} , function ( error ) {
235
- if ( $state . transition !== transition ) return ; // superseded by a new transition
256
+ if ( $state . transition !== transition ) return TransitionSuperseded ;
236
257
237
258
$state . transition = null ;
259
+ $rootScope . $broadcast ( '$stateChangeError' , to . self , toParams , from . self , fromParams , error ) ;
238
260
239
- $rootScope . $broadcast ( '$stateChangeError' , to . self , from . self , error ) ;
240
261
return $q . reject ( error ) ;
241
262
} ) ;
242
263
243
264
return transition ;
244
265
}
245
266
246
- function resolveState ( state , params , inherited , dst ) {
267
+ function resolveState ( state , params , paramsAreFiltered , inherited , dst ) {
247
268
// We need to track all the promises generated during the resolution process.
248
269
// The first of these is for the fully resolved parent locals.
249
270
var promises = [ inherited ] ;
250
271
251
- // Make a restricted $stateParams with only the parameters that apply to this state, and
252
- // force them all to strings while we're at it. In addition to being available to the
253
- // controller and onEnter/onExit callbacks, we also need $stateParams to be available
254
- // for any $injector calls we make during the dependency resolution process.
255
- var $stateParams = { } ;
272
+ // Make a restricted $stateParams with only the parameters that apply to this state if
273
+ // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
274
+ // we also need $stateParams to be available for any $injector calls we make during the
275
+ // dependency resolution process.
276
+ var $stateParams ;
277
+ if ( paramsAreFiltered ) $stateParams = params ;
278
+ else {
279
+ $stateParams = { } ;
280
+ forEach ( state . params , function ( name ) {
281
+ $stateParams [ name ] = params [ name ] ;
282
+ } ) ;
283
+ }
256
284
var locals = { $stateParams : $stateParams } ;
257
- forEach ( state . params , function ( name ) {
258
- var value = params [ name ] ;
259
- $stateParams [ name ] = ( value != null ) ? String ( value ) : null ;
260
- } ) ;
261
285
262
286
// Resolves the values from an individual 'resolve' dependency spec
263
287
function resolve ( deps , dst ) {
0 commit comments