Skip to content

Commit 39f8a53

Browse files
feat(abort): Add API to manually abort/cancel a transition
1 parent 953e618 commit 39f8a53

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

src/transition/transition.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { services } from '../common/coreservices';
88
import {
99
map, find, extend, mergeR, tail, omit, toJson, arrayTuples, unnestR, identity, anyTrueR,
1010
} from '../common/common';
11-
import { isObject } from '../common/predicates';
11+
import {isObject, isUndefined} from '../common/predicates';
1212
import { prop, propEq, val, not, is } from '../common/hof';
1313
import { StateDeclaration, StateOrName } from '../state/interface';
1414
import {
@@ -74,10 +74,14 @@ export class Transition implements IHookRegistry {
7474
* A boolean which indicates if the transition was successful
7575
*
7676
* After a successful transition, this value is set to true.
77-
* After a failed transition, this value is set to false.
77+
* After an unsuccessful transition, this value is set to false.
78+
*
79+
* The value will be undefined if the transition is not complete
7880
*/
7981
success: boolean;
8082
/** @hidden */
83+
_aborted: boolean;
84+
/** @hidden */
8185
private _error: any;
8286

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

688+
/**
689+
* Aborts this transition
690+
*
691+
* Imperative API to abort a Transition.
692+
* This only applies to Transitions that are not yet complete.
693+
*/
694+
abort() {
695+
// Do not set flag if the transition is already complete
696+
if (isUndefined(this.success)) {
697+
this._aborted = true;
698+
}
699+
}
700+
684701
/**
685702
* The Transition error reason.
686703
*

src/transition/transitionHook.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let defaultOptions: TransitionHookOptions = {
2121
current: noop,
2222
transition: null,
2323
traceData: {},
24-
bind: null
24+
bind: null,
2525
};
2626

2727
export type GetResultHandler = (hook: TransitionHook) => ResultHandler;
@@ -56,7 +56,7 @@ export class TransitionHook {
5656
isPromise(result) && result.catch(err =>
5757
hook.logError(Rejection.normalize(err)));
5858
return undefined;
59-
};
59+
}
6060

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

7171
static THROW_ERROR: GetErrorHandler = (hook: TransitionHook) => (error: any) => {
7272
throw error;
73-
};
73+
}
7474

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

167+
if (this.transition._aborted) {
168+
return Rejection.aborted().toPromise();
169+
}
170+
167171
// This transition is no longer current.
168172
// Another transition started while this hook was still running.
169173
if (this.isSuperseded()) {

test/stateServiceSpec.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ describe('stateService', function () {
497497
done();
498498
});
499499

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

545+
it('can be manually aborted', async(done) => {
546+
$state.defaultErrorHandler(() => null);
547+
548+
await initStateTo(A);
549+
550+
router.transitionService.onStart({}, trans => {
551+
if (trans.$id === 1) return new Promise(resolve => setTimeout(resolve, 50)) as any;
552+
});
553+
554+
let promise = $state.transitionTo(B, {});
555+
let transition = promise.transition;
556+
557+
setTimeout(() => transition.abort());
558+
559+
const expects = (result) => {
560+
expect($state.current).toBe(A);
561+
expect(result.type).toBe(RejectType.ABORTED);
562+
563+
done();
564+
};
565+
566+
promise.then(expects, expects);
567+
});
568+
545569
it('aborts pending transitions when superseded from callbacks', async(done) => {
546570
// router.trace.enable(1);
547571
$state.defaultErrorHandler(() => null);

0 commit comments

Comments
 (0)