-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathtransitionManager.ts
136 lines (116 loc) · 4.96 KB
/
transitionManager.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/** @module state */ /** for typedoc */
import {IPromise, IQService} from "angular";
import {copy} from "../../common/common";
import {prop} from "../../common/hof";
import {Queue} from "../../common/queue";
import {Param} from "../../params/param";
import {TreeChanges} from "../../transition/interface";
import {Transition} from "../../transition/transition";
import {TransitionRejection, RejectType} from "../../transition/rejectFactory";
import {StateService, StateDeclaration} from "../interface";
import {TargetState} from "../targetState";
import {ViewHooks} from "./viewHooks";
import {EnterExitHooks} from "./enterExitHooks";
import {ResolveHooks} from "./resolveHooks";
/**
* This class:
*
* * Takes a blank transition object and adds all the hooks necessary for it to behave like a state transition.
*
* * Runs the transition, returning a chained promise which:
* * transforms the resolved Transition.promise to the final destination state.
* * manages the rejected Transition.promise, checking for Dynamic or Redirected transitions
*
* * Registers a handler to update global $state data such as "active transitions" and "current state/params"
*
* * Registers view hooks, which maintain the list of active view configs and sync with/update the ui-views
*
* * Registers onEnter/onRetain/onExit hooks which delegate to the state's hooks of the same name, at the appropriate time
*
* * Registers eager and lazy resolve hooks
*/
export class TransitionManager {
private treeChanges: TreeChanges;
private enterExitHooks: EnterExitHooks;
private viewHooks: ViewHooks;
private resolveHooks: ResolveHooks;
constructor(
private transition: Transition,
private $transitions,
private $urlRouter,
private $view, // service
private $state: StateService,
private $stateParams, // service/obj
private $q: IQService, // TODO: get from runtime.$q
private activeTransQ: Queue<Transition>,
private changeHistory: Queue<TreeChanges>
) {
this.viewHooks = new ViewHooks(transition, $view);
this.enterExitHooks = new EnterExitHooks(transition);
this.resolveHooks = new ResolveHooks(transition);
this.treeChanges = transition.treeChanges();
this.registerUpdateGlobalState();
this.viewHooks.registerHooks();
this.enterExitHooks.registerHooks();
this.resolveHooks.registerHooks();
}
runTransition(): IPromise<any> {
this.activeTransQ.clear(); // TODO: nuke this
this.activeTransQ.enqueue(this.transition);
this.$state.transition = this.transition;
let promise = this.transition.run()
.then((trans: Transition) => trans.to()) // resolve to the final state (TODO: good? bad?)
.catch(error => this.transRejected(error)); // if rejected, handle dynamic and redirect
let always = () => {
this.activeTransQ.remove(this.transition);
if (this.$state.transition === this.transition) this.transition = null;
};
promise.then(always, always);
return promise;
}
registerUpdateGlobalState() {
this.transition.onFinish({}, this.updateGlobalState.bind(this), {priority: -10000});
}
updateGlobalState() {
let {treeChanges, transition, $state, changeHistory} = this;
// Update globals in $state
$state.$current = transition.$to();
$state.current = $state.$current.self;
changeHistory.enqueue(treeChanges);
this.updateStateParams();
}
transRejected(error): (StateDeclaration|IPromise<any>) {
let {transition, $state, $stateParams, $q} = this;
// Handle redirect and abort
if (error instanceof TransitionRejection) {
if (error.type === RejectType.IGNORED) {
// Update $stateParmas/$state.params/$location.url if transition ignored, but dynamic params have changed.
let dynamic = $state.$current.parameters().filter(prop('dynamic'));
if (!Param.equals(dynamic, $stateParams, transition.params())) {
this.updateStateParams();
}
return $state.current;
}
if (error.type === RejectType.SUPERSEDED && error.redirected && error.detail instanceof TargetState) {
return this._redirectMgr(transition.redirect(error.detail)).runTransition();
}
}
this.$transitions.defaultErrorHandler()(error);
return $q.reject(error);
}
updateStateParams() {
let {transition, $urlRouter, $state, $stateParams} = this;
let options = transition.options();
$state.params = transition.params();
copy($state.params, $stateParams);
$stateParams.$sync().$off();
if (options.location && $state.$current.navigable) {
$urlRouter.push($state.$current.navigable.url, $stateParams, { replace: options.location === 'replace' });
}
$urlRouter.update(true);
}
private _redirectMgr(redirect: Transition): TransitionManager {
let {$transitions, $urlRouter, $view, $state, $stateParams, $q, activeTransQ, changeHistory} = this;
return new TransitionManager(redirect, $transitions, $urlRouter, $view, $state, $stateParams, $q, activeTransQ, changeHistory);
}
}