Skip to content

Commit ecb5023

Browse files
committed
Allow $stateNotFound handler to return a promise-like event.retry to defer state lookup retry.
1 parent 65f76f1 commit ecb5023

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

src/state.js

+19-2
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
222222
var TransitionSuperseded = $q.reject(new Error('transition superseded'));
223223
var TransitionPrevented = $q.reject(new Error('transition prevented'));
224224
var TransitionAborted = $q.reject(new Error('transition aborted'));
225+
var TransitionFailed = $q.reject(new Error('transition failed'));
225226

226227
root.locals = { resolve: null, globals: { $stateParams: {} } };
227228
$state = {
@@ -238,7 +239,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
238239
$state.transitionTo = function transitionTo(to, toParams, options) {
239240
if (!isDefined(options)) options = (options === true || options === false) ? { location: options } : {};
240241
toParams = toParams || {};
241-
options = extend({ location: true, inherit: false, relative: null }, options);
242+
options = extend({ location: true, inherit: false, relative: null, $retry: false }, options);
242243

243244
var from = $state.$current, fromParams = $state.params, fromPath = from.path;
244245

@@ -251,6 +252,22 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
251252
var redirect = { to: to, toParams: toParams, options: options };
252253
evt = $rootScope.$broadcast('$stateNotFound', redirect, from.self, fromParams);
253254
if (evt.defaultPrevented) return TransitionAborted;
255+
256+
// Allow the handler to return a promise to defer state lookup retry
257+
if (evt.retry) {
258+
if (options.$retry) return TransitionFailed;
259+
var retryTransition = $state.transition = $q.when(evt.retry);
260+
retryTransition.then(function() {
261+
if (retryTransition !== $state.transition) return TransitionSuperseded;
262+
redirect.options.$retry = true;
263+
return $state.transitionTo(redirect.to, redirect.toParams, redirect.options);
264+
},
265+
function() {
266+
return TransitionAborted;
267+
});
268+
return retryTransition;
269+
}
270+
254271
// Always retry once if the $stateNotFound was not prevented
255272
// (handles either redirect changed or state lazy-definition)
256273
to = redirect.to;
@@ -305,7 +322,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory, $
305322
resolved = resolveState(state, toParams, state===to, resolved, locals);
306323
}
307324

308-
// Once everything is resolved, wer are ready to perform the actual transition
325+
// Once everything is resolved, we are ready to perform the actual transition
309326
// and return a promise for the new state. We also keep track of what the
310327
// current promise is, so that we can detect overlapping transitions and
311328
// keep only the outcome of the last transition.

test/stateSpec.js

+35
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,41 @@ describe('state', function () {
208208
expect($state.params).toEqual({ x: '1', y: '2', z: '3', w: '4' });
209209
}));
210210

211+
it('can defer a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
212+
initStateTo(A);
213+
var called;
214+
var deferred = $q.defer();
215+
$rootScope.$on('$stateNotFound', function (ev, redirect) {
216+
ev.retry = deferred.promise;
217+
called = true;
218+
});
219+
var promise = $state.go('AA', { a: 1 });
220+
stateProvider.state('AA', { parent: A, params: [ 'a' ]});
221+
deferred.resolve();
222+
$q.flush();
223+
expect(called).toBeTruthy();
224+
expect($state.current.name).toEqual('AA');
225+
expect($state.params).toEqual({ a: '1' });
226+
}));
227+
228+
it('can defer and supersede a state transition in $stateNotFound', inject(function ($state, $q, $rootScope) {
229+
initStateTo(A);
230+
var called;
231+
var deferred = $q.defer();
232+
$rootScope.$on('$stateNotFound', function (ev, redirect) {
233+
ev.retry = deferred.promise;
234+
called = true;
235+
});
236+
var promise = $state.go('AA', { a: 1 });
237+
$state.go(B);
238+
stateProvider.state('AA', { parent: A, params: [ 'a' ]});
239+
deferred.resolve();
240+
$q.flush();
241+
expect(called).toBeTruthy();
242+
expect($state.current).toEqual(B);
243+
expect($state.params).toEqual({});
244+
}));
245+
211246
it('triggers $stateChangeSuccess', inject(function ($state, $q, $rootScope) {
212247
initStateTo(E, { i: 'iii' });
213248
var called;

0 commit comments

Comments
 (0)