Skip to content

Commit 4fcccd8

Browse files
fix(defaultErrorHandler): Invoke handler when a transition is Canceled.
Closes #2924
1 parent 44579ec commit 4fcccd8

File tree

6 files changed

+40
-8
lines changed

6 files changed

+40
-8
lines changed

src/common/common.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -381,14 +381,17 @@ export const unnestR = (memo: any[], elem: any[]) => memo.concat(elem);
381381
export const flattenR = (memo: any[], elem: any) =>
382382
isArray(elem) ? memo.concat(elem.reduce(flattenR, [])) : pushR(memo, elem);
383383

384-
/** Reduce function that pushes an object to an array, then returns the array. Mostly just for [[flattenR]] */
384+
/**
385+
* Reduce function that pushes an object to an array, then returns the array.
386+
* Mostly just for [[flattenR]] and [[uniqR]]
387+
*/
385388
export function pushR(arr: any[], obj: any) {
386389
arr.push(obj);
387390
return arr;
388391
}
389392

390393
/** Reduce function that filters out duplicates */
391-
export const uniqR = (acc: any[], token: any) =>
394+
export const uniqR = <T> (acc: T[], token: T): T[] =>
392395
inArray(acc, token) ? acc : pushR(acc, token);
393396

394397
/**

src/state/stateService.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -291,18 +291,21 @@ export class StateService {
291291
const rejectedTransitionHandler = (transition: Transition) => (error: any): Promise<any> => {
292292
if (error instanceof Rejection) {
293293
if (error.type === RejectType.IGNORED) {
294+
// Consider ignored `Transition.run()` as a successful `transitionTo`
294295
router.urlRouter.update();
295296
return services.$q.when(globals.current);
296297
}
297298

298299
if (error.type === RejectType.SUPERSEDED && error.redirected && error.detail instanceof TargetState) {
300+
// If `Transition.run()` was redirected, allow the `transitionTo()` promise to resolve successfully
301+
// by returning the promise for the new (redirect) `Transition.run()`.
299302
let redirect: Transition = transition.redirect(error.detail);
300303
return redirect.run().catch(rejectedTransitionHandler(redirect));
301304
}
302305

303306
if (error.type === RejectType.ABORTED) {
304307
router.urlRouter.update();
305-
return services.$q.reject(error);
308+
// Fall through to default error handler
306309
}
307310
}
308311

@@ -501,6 +504,10 @@ export class StateService {
501504
* The error handler is called when a [[Transition]] is rejected or when any error occurred during the Transition.
502505
* This includes errors caused by resolves and transition hooks.
503506
*
507+
* Note:
508+
* This handler does not receive certain Transition rejections.
509+
* Redirected and Ignored Transitions are not considered to be errors by [[StateService.transitionTo]].
510+
*
504511
* The built-in default error handler logs the error to the console.
505512
*
506513
* You can provide your own custom handler.

src/transition/interface.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -624,21 +624,39 @@ export interface IHookRegistry {
624624
onSuccess(matchCriteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;
625625

626626
/**
627-
* Registers a [[TransitionHookFn]], called after a successful transition completed.
627+
* Registers a [[TransitionHookFn]], called after a transition has errored.
628628
*
629-
* Registers a transition lifecycle hook, which is invoked after a transition successfully completes.
629+
* Registers a transition lifecycle hook, which is invoked after a transition has been rejected for any reason.
630630
*
631631
* See [[TransitionHookFn]] for the signature of the function.
632632
*
633633
* The [[HookMatchCriteria]] is used to determine which Transitions the hook should be invoked for.
634634
*
635635
* ### Lifecycle
636636
*
637-
* `onError` hooks are chained off the Transition's promise (see [[Transition.promise]]).
638-
* If the Transition fails and its promise is rejected, then the `onError` hooks are invoked.
639-
* Since these hooks are run after the transition is over, their return value is ignored.
637+
* The `onError` hooks are chained off the Transition's promise (see [[Transition.promise]]).
638+
* If a Transition fails, its promise is rejected and the `onError` hooks are invoked.
640639
* The `onError` hooks are invoked in priority order.
641640
*
641+
* Since these hooks are run after the transition is over, their return value is ignored.
642+
*
643+
* A transition "errors" if it was started, but failed to complete (for any reason).
644+
* A *non-exhaustive list* of reasons a transition can error:
645+
*
646+
* - A transition was cancelled because a new transition started while it was still running
647+
* - A transition was cancelled by a Transition Hook returning false
648+
* - A transition was redirected by a Transition Hook returning a [[TargetState]]
649+
* - A transition was invalid because the target state/parameters are not valid
650+
* - A transition was ignored because the target state/parameters are exactly the current state/parameters
651+
* - A Transition Hook or resolve function threw an error
652+
* - A Transition Hook returned a rejected promise
653+
* - A resolve function returned a rejected promise
654+
*
655+
* To check the failure reason, inspect the return value of [[Transition.error]].
656+
*
657+
* Note: `onError` should be used for targeted error handling, or error recovery.
658+
* For simple catch-all error reporting, use [[StateService.defaultErrorHandler]].
659+
*
642660
* ### Return value
643661
*
644662
* Since the Transition is already completed, the hook's return value is ignored

test/ng1/stateEventsSpec.js

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ describe('UI-Router v0.2.x $state events', function () {
9595
}));
9696

9797
it('can be cancelled by preventDefault() in $stateChangeStart', inject(function ($state, $q, $rootScope) {
98+
$state.defaultErrorHandler(function() {});
9899
initStateTo(A);
99100
var called;
100101
$rootScope.$on('$stateChangeStart', function (ev) {

test/ng1/stateSpec.js

+1
Original file line numberDiff line numberDiff line change
@@ -2000,6 +2000,7 @@ describe('transition hook', function() {
20002000

20012001
// Test for #2611
20022002
it("aborts should reset the URL to the prevous state's", inject(function($transitions, $q, $state, $location) {
2003+
$state.defaultErrorHandler(function() {});
20032004
$q.flush();
20042005
$transitions.onStart({ to: 'home.foo' }, function() { return false; });
20052006
$location.path('/home/foo'); $q.flush();

test/ng1/viewHookSpec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ describe("view hooks", () => {
6161
};
6262

6363
it("can cancel a transition that would exit the view's state by returning false", () => {
64+
$state.defaultErrorHandler(function() {});
6465
ctrl.prototype.uiCanExit = function() { log += "canexit;"; return false; };
6566
initial();
6667

@@ -112,6 +113,7 @@ describe("view hooks", () => {
112113
}));
113114

114115
it("can wait for a promise and then reject the transition", inject(($timeout) => {
116+
$state.defaultErrorHandler(function() {});
115117
ctrl.prototype.uiCanExit = function() {
116118
log += "canexit;";
117119
return $timeout(() => { log += "delay;"; return false; }, 1000);

0 commit comments

Comments
 (0)