1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Transition = void 0;
|
4 | var trace_1 = require("../common/trace");
|
5 | var coreservices_1 = require("../common/coreservices");
|
6 | var strings_1 = require("../common/strings");
|
7 | var common_1 = require("../common/common");
|
8 | var predicates_1 = require("../common/predicates");
|
9 | var hof_1 = require("../common/hof");
|
10 | var interface_1 = require("./interface"); // has or is using
|
11 | var transitionHook_1 = require("./transitionHook");
|
12 | var hookRegistry_1 = require("./hookRegistry");
|
13 | var hookBuilder_1 = require("./hookBuilder");
|
14 | var pathUtils_1 = require("../path/pathUtils");
|
15 | var param_1 = require("../params/param");
|
16 | var resolvable_1 = require("../resolve/resolvable");
|
17 | var resolveContext_1 = require("../resolve/resolveContext");
|
18 | var rejectFactory_1 = require("./rejectFactory");
|
19 | var common_2 = require("../common");
|
20 | /** @internal */
|
21 | var stateSelf = hof_1.prop('self');
|
22 | /**
|
23 | * Represents a transition between two states.
|
24 | *
|
25 | * When navigating to a state, we are transitioning **from** the current state **to** the new state.
|
26 | *
|
27 | * This object contains all contextual information about the to/from states, parameters, resolves.
|
28 | * It has information about all states being entered and exited as a result of the transition.
|
29 | */
|
30 | var Transition = /** @class */ (function () {
|
31 | /**
|
32 | * Creates a new Transition object.
|
33 | *
|
34 | * If the target state is not valid, an error is thrown.
|
35 | *
|
36 | * @internal
|
37 | *
|
38 | * @param fromPath The path of [[PathNode]]s from which the transition is leaving. The last node in the `fromPath`
|
39 | * encapsulates the "from state".
|
40 | * @param targetState The target state and parameters being transitioned to (also, the transition options)
|
41 | * @param router The [[UIRouter]] instance
|
42 | * @internal
|
43 | */
|
44 | function Transition(fromPath, targetState, router) {
|
45 | var _this = this;
|
46 | /** @internal */
|
47 | this._deferred = coreservices_1.services.$q.defer();
|
48 | /**
|
49 | * This promise is resolved or rejected based on the outcome of the Transition.
|
50 | *
|
51 | * When the transition is successful, the promise is resolved
|
52 | * When the transition is unsuccessful, the promise is rejected with the [[Rejection]] or javascript error
|
53 | */
|
54 | this.promise = this._deferred.promise;
|
55 | /** @internal Holds the hook registration functions such as those passed to Transition.onStart() */
|
56 | this._registeredHooks = {};
|
57 | /** @internal */
|
58 | this._hookBuilder = new hookBuilder_1.HookBuilder(this);
|
59 | /** Checks if this transition is currently active/running. */
|
60 | this.isActive = function () { return _this.router.globals.transition === _this; };
|
61 | this.router = router;
|
62 | this._targetState = targetState;
|
63 | if (!targetState.valid()) {
|
64 | throw new Error(targetState.error());
|
65 | }
|
66 | // current() is assumed to come from targetState.options, but provide a naive implementation otherwise.
|
67 | this._options = common_1.extend({ current: hof_1.val(this) }, targetState.options());
|
68 | this.$id = router.transitionService._transitionCount++;
|
69 | var toPath = pathUtils_1.PathUtils.buildToPath(fromPath, targetState);
|
70 | this._treeChanges = pathUtils_1.PathUtils.treeChanges(fromPath, toPath, this._options.reloadState);
|
71 | this.createTransitionHookRegFns();
|
72 | var onCreateHooks = this._hookBuilder.buildHooksForPhase(interface_1.TransitionHookPhase.CREATE);
|
73 | transitionHook_1.TransitionHook.invokeHooks(onCreateHooks, function () { return null; });
|
74 | this.applyViewConfigs(router);
|
75 | }
|
76 | /** @internal */
|
77 | Transition.prototype.onBefore = function (criteria, callback, options) {
|
78 | return;
|
79 | };
|
80 | /** @inheritdoc */
|
81 | Transition.prototype.onStart = function (criteria, callback, options) {
|
82 | return;
|
83 | };
|
84 | /** @inheritdoc */
|
85 | Transition.prototype.onExit = function (criteria, callback, options) {
|
86 | return;
|
87 | };
|
88 | /** @inheritdoc */
|
89 | Transition.prototype.onRetain = function (criteria, callback, options) {
|
90 | return;
|
91 | };
|
92 | /** @inheritdoc */
|
93 | Transition.prototype.onEnter = function (criteria, callback, options) {
|
94 | return;
|
95 | };
|
96 | /** @inheritdoc */
|
97 | Transition.prototype.onFinish = function (criteria, callback, options) {
|
98 | return;
|
99 | };
|
100 | /** @inheritdoc */
|
101 | Transition.prototype.onSuccess = function (criteria, callback, options) {
|
102 | return;
|
103 | };
|
104 | /** @inheritdoc */
|
105 | Transition.prototype.onError = function (criteria, callback, options) {
|
106 | return;
|
107 | };
|
108 | /** @internal
|
109 | * Creates the transition-level hook registration functions
|
110 | * (which can then be used to register hooks)
|
111 | */
|
112 | Transition.prototype.createTransitionHookRegFns = function () {
|
113 | var _this = this;
|
114 | this.router.transitionService._pluginapi
|
115 | ._getEvents()
|
116 | .filter(function (type) { return type.hookPhase !== interface_1.TransitionHookPhase.CREATE; })
|
117 | .forEach(function (type) { return hookRegistry_1.makeEvent(_this, _this.router.transitionService, type); });
|
118 | };
|
119 | /** @internal */
|
120 | Transition.prototype.getHooks = function (hookName) {
|
121 | return this._registeredHooks[hookName];
|
122 | };
|
123 | Transition.prototype.applyViewConfigs = function (router) {
|
124 | var enteringStates = this._treeChanges.entering.map(function (node) { return node.state; });
|
125 | pathUtils_1.PathUtils.applyViewConfigs(router.transitionService.$view, this._treeChanges.to, enteringStates);
|
126 | };
|
127 | /**
|
128 | * @internal
|
129 | * @returns the internal from [State] object
|
130 | */
|
131 | Transition.prototype.$from = function () {
|
132 | return common_1.tail(this._treeChanges.from).state;
|
133 | };
|
134 | /**
|
135 | * @internal
|
136 | * @returns the internal to [State] object
|
137 | */
|
138 | Transition.prototype.$to = function () {
|
139 | return common_1.tail(this._treeChanges.to).state;
|
140 | };
|
141 | /**
|
142 | * Returns the "from state"
|
143 | *
|
144 | * Returns the state that the transition is coming *from*.
|
145 | *
|
146 | * @returns The state declaration object for the Transition's ("from state").
|
147 | */
|
148 | Transition.prototype.from = function () {
|
149 | return this.$from().self;
|
150 | };
|
151 | /**
|
152 | * Returns the "to state"
|
153 | *
|
154 | * Returns the state that the transition is going *to*.
|
155 | *
|
156 | * @returns The state declaration object for the Transition's target state ("to state").
|
157 | */
|
158 | Transition.prototype.to = function () {
|
159 | return this.$to().self;
|
160 | };
|
161 | /**
|
162 | * Gets the Target State
|
163 | *
|
164 | * A transition's [[TargetState]] encapsulates the [[to]] state, the [[params]], and the [[options]] as a single object.
|
165 | *
|
166 | * @returns the [[TargetState]] of this Transition
|
167 | */
|
168 | Transition.prototype.targetState = function () {
|
169 | return this._targetState;
|
170 | };
|
171 | /**
|
172 | * Determines whether two transitions are equivalent.
|
173 | * @deprecated
|
174 | */
|
175 | Transition.prototype.is = function (compare) {
|
176 | if (compare instanceof Transition) {
|
177 | // TODO: Also compare parameters
|
178 | return this.is({ to: compare.$to().name, from: compare.$from().name });
|
179 | }
|
180 | return !((compare.to && !hookRegistry_1.matchState(this.$to(), compare.to, this)) ||
|
181 | (compare.from && !hookRegistry_1.matchState(this.$from(), compare.from, this)));
|
182 | };
|
183 | Transition.prototype.params = function (pathname) {
|
184 | if (pathname === void 0) { pathname = 'to'; }
|
185 | return Object.freeze(this._treeChanges[pathname].map(hof_1.prop('paramValues')).reduce(common_1.mergeR, {}));
|
186 | };
|
187 | Transition.prototype.paramsChanged = function () {
|
188 | var fromParams = this.params('from');
|
189 | var toParams = this.params('to');
|
190 | // All the parameters declared on both the "to" and "from" paths
|
191 | var allParamDescriptors = []
|
192 | .concat(this._treeChanges.to)
|
193 | .concat(this._treeChanges.from)
|
194 | .map(function (pathNode) { return pathNode.paramSchema; })
|
195 | .reduce(common_2.flattenR, [])
|
196 | .reduce(common_2.uniqR, []);
|
197 | var changedParamDescriptors = param_1.Param.changed(allParamDescriptors, fromParams, toParams);
|
198 | return changedParamDescriptors.reduce(function (changedValues, descriptor) {
|
199 | changedValues[descriptor.id] = toParams[descriptor.id];
|
200 | return changedValues;
|
201 | }, {});
|
202 | };
|
203 | /**
|
204 | * Creates a [[UIInjector]] Dependency Injector
|
205 | *
|
206 | * Returns a Dependency Injector for the Transition's target state (to state).
|
207 | * The injector provides resolve values which the target state has access to.
|
208 | *
|
209 | * The `UIInjector` can also provide values from the native root/global injector (ng1/ng2).
|
210 | *
|
211 | * #### Example:
|
212 | * ```js
|
213 | * .onEnter({ entering: 'myState' }, trans => {
|
214 | * var myResolveValue = trans.injector().get('myResolve');
|
215 | * // Inject a global service from the global/native injector (if it exists)
|
216 | * var MyService = trans.injector().get('MyService');
|
217 | * })
|
218 | * ```
|
219 | *
|
220 | * In some cases (such as `onBefore`), you may need access to some resolve data but it has not yet been fetched.
|
221 | * You can use [[UIInjector.getAsync]] to get a promise for the data.
|
222 | * #### Example:
|
223 | * ```js
|
224 | * .onBefore({}, trans => {
|
225 | * return trans.injector().getAsync('myResolve').then(myResolveValue =>
|
226 | * return myResolveValue !== 'ABORT';
|
227 | * });
|
228 | * });
|
229 | * ```
|
230 | *
|
231 | * If a `state` is provided, the injector that is returned will be limited to resolve values that the provided state has access to.
|
232 | * This can be useful if both a parent state `foo` and a child state `foo.bar` have both defined a resolve such as `data`.
|
233 | * #### Example:
|
234 | * ```js
|
235 | * .onEnter({ to: 'foo.bar' }, trans => {
|
236 | * // returns result of `foo` state's `myResolve` resolve
|
237 | * // even though `foo.bar` also has a `myResolve` resolve
|
238 | * var fooData = trans.injector('foo').get('myResolve');
|
239 | * });
|
240 | * ```
|
241 | *
|
242 | * If you need resolve data from the exiting states, pass `'from'` as `pathName`.
|
243 | * The resolve data from the `from` path will be returned.
|
244 | * #### Example:
|
245 | * ```js
|
246 | * .onExit({ exiting: 'foo.bar' }, trans => {
|
247 | * // Gets the resolve value of `myResolve` from the state being exited
|
248 | * var fooData = trans.injector(null, 'from').get('myResolve');
|
249 | * });
|
250 | * ```
|
251 | *
|
252 | *
|
253 | * @param state Limits the resolves provided to only the resolves the provided state has access to.
|
254 | * @param pathName Default: `'to'`: Chooses the path for which to create the injector. Use this to access resolves for `exiting` states.
|
255 | *
|
256 | * @returns a [[UIInjector]]
|
257 | */
|
258 | Transition.prototype.injector = function (state, pathName) {
|
259 | if (pathName === void 0) { pathName = 'to'; }
|
260 | var path = this._treeChanges[pathName];
|
261 | if (state)
|
262 | path = pathUtils_1.PathUtils.subPath(path, function (node) { return node.state === state || node.state.name === state; });
|
263 | return new resolveContext_1.ResolveContext(path).injector();
|
264 | };
|
265 | /**
|
266 | * Gets all available resolve tokens (keys)
|
267 | *
|
268 | * This method can be used in conjunction with [[injector]] to inspect the resolve values
|
269 | * available to the Transition.
|
270 | *
|
271 | * This returns all the tokens defined on [[StateDeclaration.resolve]] blocks, for the states
|
272 | * in the Transition's [[TreeChanges.to]] path.
|
273 | *
|
274 | * #### Example:
|
275 | * This example logs all resolve values
|
276 | * ```js
|
277 | * let tokens = trans.getResolveTokens();
|
278 | * tokens.forEach(token => console.log(token + " = " + trans.injector().get(token)));
|
279 | * ```
|
280 | *
|
281 | * #### Example:
|
282 | * This example creates promises for each resolve value.
|
283 | * This triggers fetches of resolves (if any have not yet been fetched).
|
284 | * When all promises have all settled, it logs the resolve values.
|
285 | * ```js
|
286 | * let tokens = trans.getResolveTokens();
|
287 | * let promise = tokens.map(token => trans.injector().getAsync(token));
|
288 | * Promise.all(promises).then(values => console.log("Resolved values: " + values));
|
289 | * ```
|
290 | *
|
291 | * Note: Angular 1 users whould use `$q.all()`
|
292 | *
|
293 | * @param pathname resolve context's path name (e.g., `to` or `from`)
|
294 | *
|
295 | * @returns an array of resolve tokens (keys)
|
296 | */
|
297 | Transition.prototype.getResolveTokens = function (pathname) {
|
298 | if (pathname === void 0) { pathname = 'to'; }
|
299 | return new resolveContext_1.ResolveContext(this._treeChanges[pathname]).getTokens();
|
300 | };
|
301 | /**
|
302 | * Dynamically adds a new [[Resolvable]] (i.e., [[StateDeclaration.resolve]]) to this transition.
|
303 | *
|
304 | * Allows a transition hook to dynamically add a Resolvable to this Transition.
|
305 | *
|
306 | * Use the [[Transition.injector]] to retrieve the resolved data in subsequent hooks ([[UIInjector.get]]).
|
307 | *
|
308 | * If a `state` argument is provided, the Resolvable is processed when that state is being entered.
|
309 | * If no `state` is provided then the root state is used.
|
310 | * If the given `state` has already been entered, the Resolvable is processed when any child state is entered.
|
311 | * If no child states will be entered, the Resolvable is processed during the `onFinish` phase of the Transition.
|
312 | *
|
313 | * The `state` argument also scopes the resolved data.
|
314 | * The resolved data is available from the injector for that `state` and any children states.
|
315 | *
|
316 | * #### Example:
|
317 | * ```js
|
318 | * transitionService.onBefore({}, transition => {
|
319 | * transition.addResolvable({
|
320 | * token: 'myResolve',
|
321 | * deps: ['MyService'],
|
322 | * resolveFn: myService => myService.getData()
|
323 | * });
|
324 | * });
|
325 | * ```
|
326 | *
|
327 | * @param resolvable a [[ResolvableLiteral]] object (or a [[Resolvable]])
|
328 | * @param state the state in the "to path" which should receive the new resolve (otherwise, the root state)
|
329 | */
|
330 | Transition.prototype.addResolvable = function (resolvable, state) {
|
331 | if (state === void 0) { state = ''; }
|
332 | resolvable = hof_1.is(resolvable_1.Resolvable)(resolvable) ? resolvable : new resolvable_1.Resolvable(resolvable);
|
333 | var stateName = typeof state === 'string' ? state : state.name;
|
334 | var topath = this._treeChanges.to;
|
335 | var targetNode = common_1.find(topath, function (node) { return node.state.name === stateName; });
|
336 | var resolveContext = new resolveContext_1.ResolveContext(topath);
|
337 | resolveContext.addResolvables([resolvable], targetNode.state);
|
338 | };
|
339 | /**
|
340 | * Gets the transition from which this transition was redirected.
|
341 | *
|
342 | * If the current transition is a redirect, this method returns the transition that was redirected.
|
343 | *
|
344 | * #### Example:
|
345 | * ```js
|
346 | * let transitionA = $state.go('A').transition
|
347 | * transitionA.onStart({}, () => $state.target('B'));
|
348 | * $transitions.onSuccess({ to: 'B' }, (trans) => {
|
349 | * trans.to().name === 'B'; // true
|
350 | * trans.redirectedFrom() === transitionA; // true
|
351 | * });
|
352 | * ```
|
353 | *
|
354 | * @returns The previous Transition, or null if this Transition is not the result of a redirection
|
355 | */
|
356 | Transition.prototype.redirectedFrom = function () {
|
357 | return this._options.redirectedFrom || null;
|
358 | };
|
359 | /**
|
360 | * Gets the original transition in a redirect chain
|
361 | *
|
362 | * A transition might belong to a long chain of multiple redirects.
|
363 | * This method walks the [[redirectedFrom]] chain back to the original (first) transition in the chain.
|
364 | *
|
365 | * #### Example:
|
366 | * ```js
|
367 | * // states
|
368 | * registry.register({ name: 'A', redirectTo: 'B' });
|
369 | * registry.register({ name: 'B', redirectTo: 'C' });
|
370 | * registry.register({ name: 'C', redirectTo: 'D' });
|
371 | * registry.register({ name: 'D' });
|
372 | *
|
373 | * let transitionA = $state.go('A').transition
|
374 | *
|
375 | * $transitions.onSuccess({ to: 'D' }, (trans) => {
|
376 | * trans.to().name === 'D'; // true
|
377 | * trans.redirectedFrom().to().name === 'C'; // true
|
378 | * trans.originalTransition() === transitionA; // true
|
379 | * trans.originalTransition().to().name === 'A'; // true
|
380 | * });
|
381 | * ```
|
382 | *
|
383 | * @returns The original Transition that started a redirect chain
|
384 | */
|
385 | Transition.prototype.originalTransition = function () {
|
386 | var rf = this.redirectedFrom();
|
387 | return (rf && rf.originalTransition()) || this;
|
388 | };
|
389 | /**
|
390 | * Get the transition options
|
391 | *
|
392 | * @returns the options for this Transition.
|
393 | */
|
394 | Transition.prototype.options = function () {
|
395 | return this._options;
|
396 | };
|
397 | /**
|
398 | * Gets the states being entered.
|
399 | *
|
400 | * @returns an array of states that will be entered during this transition.
|
401 | */
|
402 | Transition.prototype.entering = function () {
|
403 | return common_1.map(this._treeChanges.entering, hof_1.prop('state')).map(stateSelf);
|
404 | };
|
405 | /**
|
406 | * Gets the states being exited.
|
407 | *
|
408 | * @returns an array of states that will be exited during this transition.
|
409 | */
|
410 | Transition.prototype.exiting = function () {
|
411 | return common_1.map(this._treeChanges.exiting, hof_1.prop('state')).map(stateSelf).reverse();
|
412 | };
|
413 | /**
|
414 | * Gets the states being retained.
|
415 | *
|
416 | * @returns an array of states that are already entered from a previous Transition, that will not be
|
417 | * exited during this Transition
|
418 | */
|
419 | Transition.prototype.retained = function () {
|
420 | return common_1.map(this._treeChanges.retained, hof_1.prop('state')).map(stateSelf);
|
421 | };
|
422 | /**
|
423 | * Get the [[ViewConfig]]s associated with this Transition
|
424 | *
|
425 | * Each state can define one or more views (template/controller), which are encapsulated as `ViewConfig` objects.
|
426 | * This method fetches the `ViewConfigs` for a given path in the Transition (e.g., "to" or "entering").
|
427 | *
|
428 | * @param pathname the name of the path to fetch views for:
|
429 | * (`'to'`, `'from'`, `'entering'`, `'exiting'`, `'retained'`)
|
430 | * @param state If provided, only returns the `ViewConfig`s for a single state in the path
|
431 | *
|
432 | * @returns a list of ViewConfig objects for the given path.
|
433 | */
|
434 | Transition.prototype.views = function (pathname, state) {
|
435 | if (pathname === void 0) { pathname = 'entering'; }
|
436 | var path = this._treeChanges[pathname];
|
437 | path = !state ? path : path.filter(hof_1.propEq('state', state));
|
438 | return path.map(hof_1.prop('views')).filter(common_1.identity).reduce(common_1.unnestR, []);
|
439 | };
|
440 | Transition.prototype.treeChanges = function (pathname) {
|
441 | return pathname ? this._treeChanges[pathname] : this._treeChanges;
|
442 | };
|
443 | /**
|
444 | * Creates a new transition that is a redirection of the current one.
|
445 | *
|
446 | * This transition can be returned from a [[TransitionService]] hook to
|
447 | * redirect a transition to a new state and/or set of parameters.
|
448 | *
|
449 | * @internal
|
450 | *
|
451 | * @returns Returns a new [[Transition]] instance.
|
452 | */
|
453 | Transition.prototype.redirect = function (targetState) {
|
454 | var redirects = 1, trans = this;
|
455 | // tslint:disable-next-line:no-conditional-assignment
|
456 | while ((trans = trans.redirectedFrom()) != null) {
|
457 | if (++redirects > 20)
|
458 | throw new Error("Too many consecutive Transition redirects (20+)");
|
459 | }
|
460 | var redirectOpts = { redirectedFrom: this, source: 'redirect' };
|
461 | // If the original transition was caused by URL sync, then use { location: 'replace' }
|
462 | // on the new transition (unless the target state explicitly specifies location: false).
|
463 | // This causes the original url to be replaced with the url for the redirect target
|
464 | // so the original url disappears from the browser history.
|
465 | if (this.options().source === 'url' && targetState.options().location !== false) {
|
466 | redirectOpts.location = 'replace';
|
467 | }
|
468 | var newOptions = common_1.extend({}, this.options(), targetState.options(), redirectOpts);
|
469 | targetState = targetState.withOptions(newOptions, true);
|
470 | var newTransition = this.router.transitionService.create(this._treeChanges.from, targetState);
|
471 | var originalEnteringNodes = this._treeChanges.entering;
|
472 | var redirectEnteringNodes = newTransition._treeChanges.entering;
|
473 | // --- Re-use resolve data from original transition ---
|
474 | // When redirecting from a parent state to a child state where the parent parameter values haven't changed
|
475 | // (because of the redirect), the resolves fetched by the original transition are still valid in the
|
476 | // redirected transition.
|
477 | //
|
478 | // This allows you to define a redirect on a parent state which depends on an async resolve value.
|
479 | // You can wait for the resolve, then redirect to a child state based on the result.
|
480 | // The redirected transition does not have to re-fetch the resolve.
|
481 | // ---------------------------------------------------------
|
482 | var nodeIsReloading = function (reloadState) { return function (node) {
|
483 | return reloadState && node.state.includes[reloadState.name];
|
484 | }; };
|
485 | // Find any "entering" nodes in the redirect path that match the original path and aren't being reloaded
|
486 | var matchingEnteringNodes = pathUtils_1.PathUtils.matching(redirectEnteringNodes, originalEnteringNodes, pathUtils_1.PathUtils.nonDynamicParams).filter(hof_1.not(nodeIsReloading(targetState.options().reloadState)));
|
487 | // Use the existing (possibly pre-resolved) resolvables for the matching entering nodes.
|
488 | matchingEnteringNodes.forEach(function (node, idx) {
|
489 | node.resolvables = originalEnteringNodes[idx].resolvables;
|
490 | });
|
491 | return newTransition;
|
492 | };
|
493 | /** @internal If a transition doesn't exit/enter any states, returns any [[Param]] whose value changed */
|
494 | Transition.prototype._changedParams = function () {
|
495 | var tc = this._treeChanges;
|
496 | /** Return undefined if it's not a "dynamic" transition, for the following reasons */
|
497 | // If user explicitly wants a reload
|
498 | if (this._options.reload)
|
499 | return undefined;
|
500 | // If any states are exiting or entering
|
501 | if (tc.exiting.length || tc.entering.length)
|
502 | return undefined;
|
503 | // If to/from path lengths differ
|
504 | if (tc.to.length !== tc.from.length)
|
505 | return undefined;
|
506 | // If the to/from paths are different
|
507 | var pathsDiffer = common_1.arrayTuples(tc.to, tc.from)
|
508 | .map(function (tuple) { return tuple[0].state !== tuple[1].state; })
|
509 | .reduce(common_1.anyTrueR, false);
|
510 | if (pathsDiffer)
|
511 | return undefined;
|
512 | // Find any parameter values that differ
|
513 | var nodeSchemas = tc.to.map(function (node) { return node.paramSchema; });
|
514 | var _a = [tc.to, tc.from].map(function (path) { return path.map(function (x) { return x.paramValues; }); }), toValues = _a[0], fromValues = _a[1];
|
515 | var tuples = common_1.arrayTuples(nodeSchemas, toValues, fromValues);
|
516 | return tuples.map(function (_a) {
|
517 | var schema = _a[0], toVals = _a[1], fromVals = _a[2];
|
518 | return param_1.Param.changed(schema, toVals, fromVals);
|
519 | }).reduce(common_1.unnestR, []);
|
520 | };
|
521 | /**
|
522 | * Returns true if the transition is dynamic.
|
523 | *
|
524 | * A transition is dynamic if no states are entered nor exited, but at least one dynamic parameter has changed.
|
525 | *
|
526 | * @returns true if the Transition is dynamic
|
527 | */
|
528 | Transition.prototype.dynamic = function () {
|
529 | var changes = this._changedParams();
|
530 | return !changes ? false : changes.map(function (x) { return x.dynamic; }).reduce(common_1.anyTrueR, false);
|
531 | };
|
532 | /**
|
533 | * Returns true if the transition is ignored.
|
534 | *
|
535 | * A transition is ignored if no states are entered nor exited, and no parameter values have changed.
|
536 | *
|
537 | * @returns true if the Transition is ignored.
|
538 | */
|
539 | Transition.prototype.ignored = function () {
|
540 | return !!this._ignoredReason();
|
541 | };
|
542 | /** @internal */
|
543 | Transition.prototype._ignoredReason = function () {
|
544 | var pending = this.router.globals.transition;
|
545 | var reloadState = this._options.reloadState;
|
546 | var same = function (pathA, pathB) {
|
547 | if (pathA.length !== pathB.length)
|
548 | return false;
|
549 | var matching = pathUtils_1.PathUtils.matching(pathA, pathB);
|
550 | return pathA.length === matching.filter(function (node) { return !reloadState || !node.state.includes[reloadState.name]; }).length;
|
551 | };
|
552 | var newTC = this.treeChanges();
|
553 | var pendTC = pending && pending.treeChanges();
|
554 | if (pendTC && same(pendTC.to, newTC.to) && same(pendTC.exiting, newTC.exiting))
|
555 | return 'SameAsPending';
|
556 | if (newTC.exiting.length === 0 && newTC.entering.length === 0 && same(newTC.from, newTC.to))
|
557 | return 'SameAsCurrent';
|
558 | };
|
559 | /**
|
560 | * Runs the transition
|
561 | *
|
562 | * This method is generally called from the [[StateService.transitionTo]]
|
563 | *
|
564 | * @internal
|
565 | *
|
566 | * @returns a promise for a successful transition.
|
567 | */
|
568 | Transition.prototype.run = function () {
|
569 | var _this = this;
|
570 | var runAllHooks = transitionHook_1.TransitionHook.runAllHooks;
|
571 | // Gets transition hooks array for the given phase
|
572 | var getHooksFor = function (phase) { return _this._hookBuilder.buildHooksForPhase(phase); };
|
573 | // When the chain is complete, then resolve or reject the deferred
|
574 | var transitionSuccess = function () {
|
575 | trace_1.trace.traceSuccess(_this.$to(), _this);
|
576 | _this.success = true;
|
577 | _this._deferred.resolve(_this.to());
|
578 | runAllHooks(getHooksFor(interface_1.TransitionHookPhase.SUCCESS));
|
579 | };
|
580 | var transitionError = function (reason) {
|
581 | trace_1.trace.traceError(reason, _this);
|
582 | _this.success = false;
|
583 | _this._deferred.reject(reason);
|
584 | _this._error = reason;
|
585 | runAllHooks(getHooksFor(interface_1.TransitionHookPhase.ERROR));
|
586 | };
|
587 | var runTransition = function () {
|
588 | // Wait to build the RUN hook chain until the BEFORE hooks are done
|
589 | // This allows a BEFORE hook to dynamically add additional RUN hooks via the Transition object.
|
590 | var allRunHooks = getHooksFor(interface_1.TransitionHookPhase.RUN);
|
591 | var done = function () { return coreservices_1.services.$q.when(undefined); };
|
592 | return transitionHook_1.TransitionHook.invokeHooks(allRunHooks, done);
|
593 | };
|
594 | var startTransition = function () {
|
595 | var globals = _this.router.globals;
|
596 | globals.lastStartedTransitionId = _this.$id;
|
597 | globals.transition = _this;
|
598 | globals.transitionHistory.enqueue(_this);
|
599 | trace_1.trace.traceTransitionStart(_this);
|
600 | return coreservices_1.services.$q.when(undefined);
|
601 | };
|
602 | var allBeforeHooks = getHooksFor(interface_1.TransitionHookPhase.BEFORE);
|
603 | transitionHook_1.TransitionHook.invokeHooks(allBeforeHooks, startTransition)
|
604 | .then(runTransition)
|
605 | .then(transitionSuccess, transitionError);
|
606 | return this.promise;
|
607 | };
|
608 | /**
|
609 | * Checks if the Transition is valid
|
610 | *
|
611 | * @returns true if the Transition is valid
|
612 | */
|
613 | Transition.prototype.valid = function () {
|
614 | return !this.error() || this.success !== undefined;
|
615 | };
|
616 | /**
|
617 | * Aborts this transition
|
618 | *
|
619 | * Imperative API to abort a Transition.
|
620 | * This only applies to Transitions that are not yet complete.
|
621 | */
|
622 | Transition.prototype.abort = function () {
|
623 | // Do not set flag if the transition is already complete
|
624 | if (predicates_1.isUndefined(this.success)) {
|
625 | this._aborted = true;
|
626 | }
|
627 | };
|
628 | /**
|
629 | * The Transition error reason.
|
630 | *
|
631 | * If the transition is invalid (and could not be run), returns the reason the transition is invalid.
|
632 | * If the transition was valid and ran, but was not successful, returns the reason the transition failed.
|
633 | *
|
634 | * @returns a transition rejection explaining why the transition is invalid, or the reason the transition failed.
|
635 | */
|
636 | Transition.prototype.error = function () {
|
637 | var state = this.$to();
|
638 | if (state.self.abstract) {
|
639 | return rejectFactory_1.Rejection.invalid("Cannot transition to abstract state '" + state.name + "'");
|
640 | }
|
641 | var paramDefs = state.parameters();
|
642 | var values = this.params();
|
643 | var invalidParams = paramDefs.filter(function (param) { return !param.validates(values[param.id]); });
|
644 | if (invalidParams.length) {
|
645 | var invalidValues = invalidParams.map(function (param) { return "[" + param.id + ":" + strings_1.stringify(values[param.id]) + "]"; }).join(', ');
|
646 | var detail = "The following parameter values are not valid for state '" + state.name + "': " + invalidValues;
|
647 | return rejectFactory_1.Rejection.invalid(detail);
|
648 | }
|
649 | if (this.success === false)
|
650 | return this._error;
|
651 | };
|
652 | /**
|
653 | * A string representation of the Transition
|
654 | *
|
655 | * @returns A string representation of the Transition
|
656 | */
|
657 | Transition.prototype.toString = function () {
|
658 | var fromStateOrName = this.from();
|
659 | var toStateOrName = this.to();
|
660 | var avoidEmptyHash = function (params) {
|
661 | return params['#'] !== null && params['#'] !== undefined ? params : common_1.omit(params, ['#']);
|
662 | };
|
663 | // (X) means the to state is invalid.
|
664 | var id = this.$id, from = predicates_1.isObject(fromStateOrName) ? fromStateOrName.name : fromStateOrName, fromParams = strings_1.stringify(avoidEmptyHash(this._treeChanges.from.map(hof_1.prop('paramValues')).reduce(common_1.mergeR, {}))), toValid = this.valid() ? '' : '(X) ', to = predicates_1.isObject(toStateOrName) ? toStateOrName.name : toStateOrName, toParams = strings_1.stringify(avoidEmptyHash(this.params()));
|
665 | return "Transition#" + id + "( '" + from + "'" + fromParams + " -> " + toValid + "'" + to + "'" + toParams + " )";
|
666 | };
|
667 | /** @internal */
|
668 | Transition.diToken = Transition;
|
669 | return Transition;
|
670 | }());
|
671 | exports.Transition = Transition;
|
672 | //# sourceMappingURL=transition.js.map |
\ | No newline at end of file |