Skip to content

Commit

Permalink
fix(defaultErrorHandler): Invoke handler when a transition is Canceled.
Browse files Browse the repository at this point in the history
Closes #2924
  • Loading branch information
christopherthielen committed Aug 31, 2016
1 parent 44579ec commit 4fcccd8
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 8 deletions.
7 changes: 5 additions & 2 deletions src/common/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,14 +381,17 @@ export const unnestR = (memo: any[], elem: any[]) => memo.concat(elem);
export const flattenR = (memo: any[], elem: any) =>
isArray(elem) ? memo.concat(elem.reduce(flattenR, [])) : pushR(memo, elem);

/** Reduce function that pushes an object to an array, then returns the array. Mostly just for [[flattenR]] */
/**
* Reduce function that pushes an object to an array, then returns the array.
* Mostly just for [[flattenR]] and [[uniqR]]
*/
export function pushR(arr: any[], obj: any) {
arr.push(obj);
return arr;
}

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

/**
Expand Down
9 changes: 8 additions & 1 deletion src/state/stateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,21 @@ export class StateService {
const rejectedTransitionHandler = (transition: Transition) => (error: any): Promise<any> => {
if (error instanceof Rejection) {
if (error.type === RejectType.IGNORED) {
// Consider ignored `Transition.run()` as a successful `transitionTo`
router.urlRouter.update();
return services.$q.when(globals.current);
}

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

if (error.type === RejectType.ABORTED) {
router.urlRouter.update();
return services.$q.reject(error);
// Fall through to default error handler
}
}

Expand Down Expand Up @@ -501,6 +504,10 @@ export class StateService {
* The error handler is called when a [[Transition]] is rejected or when any error occurred during the Transition.
* This includes errors caused by resolves and transition hooks.
*
* Note:
* This handler does not receive certain Transition rejections.
* Redirected and Ignored Transitions are not considered to be errors by [[StateService.transitionTo]].
*
* The built-in default error handler logs the error to the console.
*
* You can provide your own custom handler.
Expand Down
28 changes: 23 additions & 5 deletions src/transition/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -624,21 +624,39 @@ export interface IHookRegistry {
onSuccess(matchCriteria: HookMatchCriteria, callback: TransitionHookFn, options?: HookRegOptions): Function;

/**
* Registers a [[TransitionHookFn]], called after a successful transition completed.
* Registers a [[TransitionHookFn]], called after a transition has errored.
*
* Registers a transition lifecycle hook, which is invoked after a transition successfully completes.
* Registers a transition lifecycle hook, which is invoked after a transition has been rejected for any reason.
*
* See [[TransitionHookFn]] for the signature of the function.
*
* The [[HookMatchCriteria]] is used to determine which Transitions the hook should be invoked for.
*
* ### Lifecycle
*
* `onError` hooks are chained off the Transition's promise (see [[Transition.promise]]).
* If the Transition fails and its promise is rejected, then the `onError` hooks are invoked.
* Since these hooks are run after the transition is over, their return value is ignored.
* The `onError` hooks are chained off the Transition's promise (see [[Transition.promise]]).
* If a Transition fails, its promise is rejected and the `onError` hooks are invoked.
* The `onError` hooks are invoked in priority order.
*
* Since these hooks are run after the transition is over, their return value is ignored.
*
* A transition "errors" if it was started, but failed to complete (for any reason).
* A *non-exhaustive list* of reasons a transition can error:
*
* - A transition was cancelled because a new transition started while it was still running
* - A transition was cancelled by a Transition Hook returning false
* - A transition was redirected by a Transition Hook returning a [[TargetState]]
* - A transition was invalid because the target state/parameters are not valid
* - A transition was ignored because the target state/parameters are exactly the current state/parameters
* - A Transition Hook or resolve function threw an error
* - A Transition Hook returned a rejected promise
* - A resolve function returned a rejected promise
*
* To check the failure reason, inspect the return value of [[Transition.error]].
*
* Note: `onError` should be used for targeted error handling, or error recovery.
* For simple catch-all error reporting, use [[StateService.defaultErrorHandler]].
*
* ### Return value
*
* Since the Transition is already completed, the hook's return value is ignored
Expand Down
1 change: 1 addition & 0 deletions test/ng1/stateEventsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ describe('UI-Router v0.2.x $state events', function () {
}));

it('can be cancelled by preventDefault() in $stateChangeStart', inject(function ($state, $q, $rootScope) {
$state.defaultErrorHandler(function() {});
initStateTo(A);
var called;
$rootScope.$on('$stateChangeStart', function (ev) {
Expand Down
1 change: 1 addition & 0 deletions test/ng1/stateSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,7 @@ describe('transition hook', function() {

// Test for #2611
it("aborts should reset the URL to the prevous state's", inject(function($transitions, $q, $state, $location) {
$state.defaultErrorHandler(function() {});
$q.flush();
$transitions.onStart({ to: 'home.foo' }, function() { return false; });
$location.path('/home/foo'); $q.flush();
Expand Down
2 changes: 2 additions & 0 deletions test/ng1/viewHookSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe("view hooks", () => {
};

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

Expand Down Expand Up @@ -112,6 +113,7 @@ describe("view hooks", () => {
}));

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

0 comments on commit 4fcccd8

Please sign in to comment.