Skip to content

Commit

Permalink
feat(abort): Add API to manually abort/cancel a transition
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Apr 20, 2017
1 parent 953e618 commit 39f8a53
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 6 deletions.
21 changes: 19 additions & 2 deletions src/transition/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { services } from '../common/coreservices';
import {
map, find, extend, mergeR, tail, omit, toJson, arrayTuples, unnestR, identity, anyTrueR,
} from '../common/common';
import { isObject } from '../common/predicates';
import {isObject, isUndefined} from '../common/predicates';
import { prop, propEq, val, not, is } from '../common/hof';
import { StateDeclaration, StateOrName } from '../state/interface';
import {
Expand Down Expand Up @@ -74,10 +74,14 @@ export class Transition implements IHookRegistry {
* A boolean which indicates if the transition was successful
*
* After a successful transition, this value is set to true.
* After a failed transition, this value is set to false.
* After an unsuccessful transition, this value is set to false.
*
* The value will be undefined if the transition is not complete
*/
success: boolean;
/** @hidden */
_aborted: boolean;
/** @hidden */
private _error: any;

/** @hidden Holds the hook registration functions such as those passed to Transition.onStart() */
Expand Down Expand Up @@ -681,6 +685,19 @@ export class Transition implements IHookRegistry {
return !this.error() || this.success !== undefined;
}

/**
* Aborts this transition
*
* Imperative API to abort a Transition.
* This only applies to Transitions that are not yet complete.
*/
abort() {
// Do not set flag if the transition is already complete
if (isUndefined(this.success)) {
this._aborted = true;
}
}

/**
* The Transition error reason.
*
Expand Down
10 changes: 7 additions & 3 deletions src/transition/transitionHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ let defaultOptions: TransitionHookOptions = {
current: noop,
transition: null,
traceData: {},
bind: null
bind: null,
};

export type GetResultHandler = (hook: TransitionHook) => ResultHandler;
Expand Down Expand Up @@ -56,7 +56,7 @@ export class TransitionHook {
isPromise(result) && result.catch(err =>
hook.logError(Rejection.normalize(err)));
return undefined;
};
}

/**
* These GetErrorHandler(s) are used by [[invokeHook]] below
Expand All @@ -70,7 +70,7 @@ export class TransitionHook {

static THROW_ERROR: GetErrorHandler = (hook: TransitionHook) => (error: any) => {
throw error;
};
}

private isSuperseded = () =>
this.type.hookPhase === TransitionHookPhase.RUN && !this.options.transition.isActive();
Expand Down Expand Up @@ -164,6 +164,10 @@ export class TransitionHook {
return Rejection.aborted(`UIRouter instance #${router.$id} has been stopped (disposed)`).toPromise();
}

if (this.transition._aborted) {
return Rejection.aborted().toPromise();
}

// This transition is no longer current.
// Another transition started while this hook was still running.
if (this.isSuperseded()) {
Expand Down
26 changes: 25 additions & 1 deletion test/stateServiceSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ describe('stateService', function () {
done();
});

it('is a no-op when passing the current state and identical parameters', async(done) => {
it('is ignored when passing the current state and identical parameters', async(done) => {
let enterlog = "";
$transitions.onEnter({ entering: 'A'}, (trans, state) => { enterlog += state.name + ";" });
await initStateTo(A);
Expand Down Expand Up @@ -542,6 +542,30 @@ describe('stateService', function () {
done();
});

it('can be manually aborted', async(done) => {
$state.defaultErrorHandler(() => null);

await initStateTo(A);

router.transitionService.onStart({}, trans => {
if (trans.$id === 1) return new Promise(resolve => setTimeout(resolve, 50)) as any;
});

let promise = $state.transitionTo(B, {});
let transition = promise.transition;

setTimeout(() => transition.abort());

const expects = (result) => {
expect($state.current).toBe(A);
expect(result.type).toBe(RejectType.ABORTED);

done();
};

promise.then(expects, expects);
});

it('aborts pending transitions when superseded from callbacks', async(done) => {
// router.trace.enable(1);
$state.defaultErrorHandler(() => null);
Expand Down

0 comments on commit 39f8a53

Please sign in to comment.