Skip to content

Commit 771c4ab

Browse files
refactor(TransitionManager): Remove TransitionManager. Handle redirects in StateService
1 parent 221103e commit 771c4ab

File tree

5 files changed

+127
-140
lines changed

5 files changed

+127
-140
lines changed

src/hooks/transitionManager.ts

+43-76
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,43 @@
1-
/** @module state */ /** for typedoc */
2-
import {Transition} from "../transition/transition";
3-
import {Rejection, RejectType} from "../transition/rejectFactory";
4-
5-
import {StateDeclaration} from "../state/interface";
6-
import {StateService} from "../state/stateService";
7-
import {TargetState} from "../state/targetState";
8-
import {UrlRouter} from "../url/urlRouter";
9-
import {services} from "../common/coreservices";
10-
import {Globals} from "../globals";
11-
import {ViewService} from "../view/view";
12-
import {TransitionService} from "../transition/transitionService";
13-
14-
/**
15-
* Adds the built-in UI-Router hooks to a [[Transition]]
16-
*
17-
* * Takes a blank transition object and adds all the hooks necessary for it to behave like a state transition.
18-
*
19-
* * Runs the transition, returning a chained promise which:
20-
* * transforms the resolved Transition.promise to the final destination state.
21-
* * manages the rejected Transition.promise, checking for Dynamic or Redirected transitions
22-
*
23-
* * Registers a handler to update global $state data such as "active transitions" and "current state/params"
24-
*
25-
* * Registers view hooks, which maintain the list of active view configs and sync with/update the ui-views
26-
*
27-
* * Registers onEnter/onRetain/onExit hooks which delegate to the state's hooks of the same name, at the appropriate time
28-
*
29-
* * Registers eager and lazy resolve hooks
30-
*/
31-
export class TransitionManager {
32-
private $q;
33-
private getRedirectedMgr: (redirect: Transition) => TransitionManager;
34-
35-
constructor(
36-
private transition: Transition,
37-
private $transitions: TransitionService,
38-
private $urlRouter: UrlRouter,
39-
$view: ViewService,
40-
private $state: StateService,
41-
private globals: Globals
42-
) {
43-
this.$q = services.$q;
44-
this.getRedirectedMgr = (redirect: Transition) =>
45-
new TransitionManager(redirect, $transitions, $urlRouter, $view, $state, globals);
46-
}
47-
48-
runTransition(): Promise<any> {
49-
this.globals.transitionHistory.enqueue(this.transition);
50-
return this.transition.run()
51-
.then((trans: Transition) => trans.to()) // resolve to the final state (TODO: good? bad?)
52-
.catch(error => this.transRejected(error)); // if rejected, handle dynamic and redirect
53-
}
54-
55-
transRejected(error): (StateDeclaration|Promise<any>) {
56-
// Handle redirect and abort
57-
if (error instanceof Rejection) {
58-
if (error.type === RejectType.IGNORED) {
59-
this.$urlRouter.update();
60-
return this.$state.current;
61-
}
62-
63-
if (error.type === RejectType.SUPERSEDED && error.redirected && error.detail instanceof TargetState) {
64-
return this.getRedirectedMgr(this.transition.redirect(error.detail)).runTransition();
65-
}
66-
67-
if (error.type === RejectType.ABORTED) {
68-
this.$urlRouter.update();
69-
}
70-
}
71-
72-
this.$transitions.defaultErrorHandler()(error);
73-
74-
return this.$q.reject(error);
75-
}
76-
}
1+
// /** @module state */ /** for typedoc */
2+
// import {Transition} from "../transition/transition";
3+
// import {Rejection, RejectType} from "../transition/rejectFactory";
4+
//
5+
// import {StateDeclaration} from "../state/interface";
6+
// import {TargetState} from "../state/targetState";
7+
// import {services} from "../common/coreservices";
8+
// import {UiRouter} from "../router";
9+
//
10+
// /** Manages Ignored, Cancelled and Redirected transitions */
11+
// export class TransitionManager {
12+
// private $q;
13+
//
14+
// constructor(private transition: Transition, private router: UiRouter) {
15+
// this.$q = services.$q;
16+
// }
17+
//
18+
// runTransition(): Promise<any> {
19+
// return this.transition.run()
20+
// .catch(error => this.transRejected(error)); // if rejected, handle dynamic and redirect
21+
// }
22+
//
23+
// transRejected(error): (StateDeclaration|Promise<any>) {
24+
// // Handle redirect and abort
25+
// if (error instanceof Rejection) {
26+
// if (error.type === RejectType.IGNORED) {
27+
// this.router.urlRouter.update();
28+
// return this.router.globals.current;
29+
// }
30+
//
31+
// if (error.type === RejectType.SUPERSEDED && error.redirected && error.detail instanceof TargetState) {
32+
// let redirectManager = new TransitionManager(this.transition.redirect(error.detail), this.router);
33+
// return redirectManager.runTransition();
34+
// }
35+
//
36+
// if (error.type === RejectType.ABORTED) {
37+
// this.router.urlRouter.update();
38+
// }
39+
// }
40+
//
41+
// return this.$q.reject(error);
42+
// }
43+
// }

src/router.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class UiRouter {
3636
/** @hidden TODO: move this to ng1.ts */
3737
stateProvider = new StateProvider(this.stateRegistry);
3838

39-
stateService = new StateService(this.viewService, this.urlRouter, this.transitionService, this.stateRegistry, this.stateProvider, <Globals> this.globals);
39+
stateService = new StateService(this);
4040

4141
constructor() {
4242
this.viewService.rootContext(this.stateRegistry.root());

src/state/module.ts

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
export * from "./interface";
33
export * from "./state";
44
export * from "./stateBuilder";
5-
export * from "../hooks/transitionManager";
65
export * from "./stateObject";
76
export * from "./stateMatcher";
87
export * from "./stateQueueManager";

src/state/stateService.ts

+62-40
Original file line numberDiff line numberDiff line change
@@ -7,45 +7,33 @@ import {services} from "../common/coreservices";
77
import {PathFactory} from "../path/pathFactory";
88
import {PathNode} from "../path/node";
99

10-
import {ViewService} from "../view/view";
11-
12-
import {StateParams} from "../params/stateParams";
13-
14-
import {UrlRouter} from "../url/urlRouter";
15-
1610
import {TransitionOptions} from "../transition/interface";
17-
import {TransitionService, defaultTransOpts} from "../transition/transitionService";
18-
import {Rejection} from "../transition/rejectFactory";
11+
import {defaultTransOpts} from "../transition/transitionService";
12+
import {Rejection, RejectType} from "../transition/rejectFactory";
1913
import {Transition} from "../transition/transition";
2014

2115
import {StateOrName, StateDeclaration, TransitionPromise} from "./interface";
22-
import {StateRegistry} from "./stateRegistry";
2316
import {State} from "./stateObject";
2417
import {TargetState} from "./targetState";
2518

2619
import {RawParams} from "../params/interface";
2720
import {ParamsOrArray} from "../params/interface";
28-
import {TransitionManager} from "../hooks/transitionManager";
2921
import {Param} from "../params/param";
3022
import {Glob} from "../common/glob";
3123
import {equalForKeys} from "../common/common";
3224
import {HrefOptions} from "./interface";
33-
import {StateProvider} from "./state";
3425
import {bindFunctions} from "../common/common";
35-
import {UiRouterGlobals, Globals} from "../globals";
26+
import {Globals} from "../globals";
27+
import {UiRouter} from "../router";
28+
import {StateParams} from "../params/stateParams"; // for params() return type
3629

3730
export class StateService {
38-
get transition() { return this.globals.transition; }
39-
get params() { return this.globals.params; }
40-
get current() { return this.globals.current; }
41-
get $current() { return this.globals.$current; }
42-
43-
constructor(private $view: ViewService,
44-
private $urlRouter: UrlRouter,
45-
private $transitions: TransitionService,
46-
private stateRegistry: StateRegistry,
47-
private stateProvider: StateProvider,
48-
private globals: Globals) {
31+
get transition() { return this.router.globals.transition; }
32+
get params() { return this.router.globals.params; }
33+
get current() { return this.router.globals.current; }
34+
get $current() { return this.router.globals.$current; }
35+
36+
constructor(private router: UiRouter) {
4937
let getters = ['current', '$current', 'params', 'transition'];
5038
let boundFns = Object.keys(StateService.prototype).filter(key => getters.indexOf(key) === -1);
5139
bindFunctions(StateService.prototype, this, this, boundFns);
@@ -60,10 +48,11 @@ export class StateService {
6048
* the result returned.
6149
*/
6250
private _handleInvalidTargetState(fromPath: PathNode[], $to$: TargetState) {
63-
const latestThing = () => this.globals.transitionHistory.peekTail();
51+
let globals = <Globals> this.router.globals;
52+
const latestThing = () => globals.transitionHistory.peekTail();
6453
let latest = latestThing();
6554
let $from$ = PathFactory.makeTargetState(fromPath);
66-
let callbackQueue = new Queue<Function>([].concat(this.stateProvider.invalidCallbacks));
55+
let callbackQueue = new Queue<Function>([].concat(this.router.stateProvider.invalidCallbacks));
6756
let {$q, $injector} = services;
6857

6958
const invokeCallback = (callback: Function) => $q.when($injector.invoke(callback, null, { $to$, $from$ }));
@@ -221,12 +210,13 @@ export class StateService {
221210
// If we're reloading, find the state object to reload from
222211
if (isObject(options.reload) && !(<any>options.reload).name)
223212
throw new Error('Invalid reload state object');
224-
options.reloadState = options.reload === true ? this.stateRegistry.root() : this.stateRegistry.matcher.find(<any> options.reload, options.relative);
213+
let reg = this.router.stateRegistry;
214+
options.reloadState = options.reload === true ? reg.root() : reg.matcher.find(<any> options.reload, options.relative);
225215

226216
if (options.reload && !options.reloadState)
227217
throw new Error(`No such reload state '${(isString(options.reload) ? options.reload : (<any>options.reload).name)}'`);
228218

229-
let stateDefinition = this.stateRegistry.matcher.find(identifier, options.relative);
219+
let stateDefinition = reg.matcher.find(identifier, options.relative);
230220
return new TargetState(identifier, stateDefinition, params, options);
231221
};
232222

@@ -269,25 +259,56 @@ export class StateService {
269259
* {@link ui.router.state.$state#methods_go $state.go}.
270260
*/
271261
transitionTo(to: StateOrName, toParams: RawParams = {}, options: TransitionOptions = {}): TransitionPromise {
272-
let transHistory = this.globals.transitionHistory;
262+
let router = this.router;
263+
let globals = <Globals> router.globals;
264+
let transHistory = globals.transitionHistory;
273265
options = defaults(options, defaultTransOpts);
274266
options = extend(options, { current: transHistory.peekTail.bind(transHistory)});
275267

276268
let ref: TargetState = this.target(to, toParams, options);
277-
let latestSuccess: Transition = this.globals.successfulTransitions.peekTail();
278-
const rootPath = () => [ new PathNode(this.stateRegistry.root()) ];
269+
let latestSuccess: Transition = globals.successfulTransitions.peekTail();
270+
const rootPath = () => [ new PathNode(this.router.stateRegistry.root()) ];
279271
let currentPath: PathNode[] = latestSuccess ? latestSuccess.treeChanges().to : rootPath();
280272

281273
if (!ref.exists())
282274
return this._handleInvalidTargetState(currentPath, ref);
275+
283276
if (!ref.valid())
284277
return services.$q.reject(ref.error());
285278

286-
let transition = this.$transitions.create(currentPath, ref);
287-
let tMgr = new TransitionManager(transition, this.$transitions, this.$urlRouter, this.$view, <StateService> this, this.globals);
288-
let transitionPromise = tMgr.runTransition();
279+
/**
280+
* Special handling for Ignored, Aborted, and Redirected transitions
281+
*
282+
* The semantics for the transition.run() promise and the StateService.transitionTo()
283+
* promise differ. For instance, the run() promise may be rejected because it was
284+
* IGNORED, but the transitionTo() promise is resolved because from the user perspective
285+
* no error occurred. Likewise, the transition.run() promise may be rejected because of
286+
* a Redirect, but the transitionTo() promise is chained to the new Transition's promise.
287+
*/
288+
const rejectedTransitionHandler = (transition) => (error) => {
289+
if (error instanceof Rejection) {
290+
if (error.type === RejectType.IGNORED) {
291+
router.urlRouter.update();
292+
return globals.current;
293+
}
294+
295+
if (error.type === RejectType.SUPERSEDED && error.redirected && error.detail instanceof TargetState) {
296+
let redirect: Transition = transition.redirect(error.detail);
297+
return redirect.run().catch(rejectedTransitionHandler(redirect));
298+
}
299+
300+
if (error.type === RejectType.ABORTED) {
301+
router.urlRouter.update();
302+
}
303+
}
304+
305+
return services.$q.reject(error);
306+
};
307+
308+
let transition = this.router.transitionService.create(currentPath, ref);
309+
let transitionToPromise = transition.run().catch(rejectedTransitionHandler(transition));
289310
// Return a promise for the transition, which also has the transition object on it.
290-
return extend(transitionPromise, { transition });
311+
return extend(transitionToPromise, { transition });
291312
};
292313

293314
/**
@@ -326,7 +347,7 @@ export class StateService {
326347
*/
327348
is(stateOrName: StateOrName, params?: RawParams, options?: TransitionOptions): boolean {
328349
options = defaults(options, { relative: this.$current });
329-
let state = this.stateRegistry.matcher.find(stateOrName, options.relative);
350+
let state = this.router.stateRegistry.matcher.find(stateOrName, options.relative);
330351
if (!isDefined(state)) return undefined;
331352
if (this.$current !== state) return false;
332353
return isDefined(params) && params !== null ? Param.equals(state.parameters(), this.params, params) : true;
@@ -391,7 +412,7 @@ export class StateService {
391412
if (!glob.matches(this.$current.name)) return false;
392413
stateOrName = this.$current.name;
393414
}
394-
let state = this.stateRegistry.matcher.find(stateOrName, options.relative), include = this.$current.includes;
415+
let state = this.router.stateRegistry.matcher.find(stateOrName, options.relative), include = this.$current.includes;
395416

396417
if (!isDefined(state)) return undefined;
397418
if (!isDefined(include[state.name])) return false;
@@ -436,7 +457,7 @@ export class StateService {
436457
};
437458
options = defaults(options, defaultHrefOpts);
438459

439-
let state = this.stateRegistry.matcher.find(stateOrName, options.relative);
460+
let state = this.router.stateRegistry.matcher.find(stateOrName, options.relative);
440461

441462
if (!isDefined(state)) return null;
442463
if (options.inherit) params = <any> this.params.$inherit(params || {}, this.$current, state);
@@ -446,7 +467,7 @@ export class StateService {
446467
if (!nav || nav.url === undefined || nav.url === null) {
447468
return null;
448469
}
449-
return this.$urlRouter.href(nav.url, Param.values(state.parameters(), params), {
470+
return this.router.urlRouter.href(nav.url, Param.values(state.parameters(), params), {
450471
absolute: options.absolute
451472
});
452473
};
@@ -468,7 +489,8 @@ export class StateService {
468489
get(stateOrName: StateOrName): StateDeclaration;
469490
get(stateOrName: StateOrName, base: StateOrName): StateDeclaration;
470491
get(stateOrName?: StateOrName, base?: StateOrName): any {
471-
if (arguments.length === 0) return this.stateRegistry.get();
472-
return this.stateRegistry.get(stateOrName, base || this.$current);
492+
let reg = this.router.stateRegistry;
493+
if (arguments.length === 0) return reg.get();
494+
return reg.get(stateOrName, base || this.$current);
473495
}
474496
}

0 commit comments

Comments
 (0)