UNPKG

@uirouter/core

Version:

UI-Router Core: Framework agnostic, State-based routing for JavaScript Single Page Apps

197 lines 8.8 kB
import { TransitionHookPhase } from './interface'; import { defaults, noop, silentRejection } from '../common/common'; import { fnToString, maxLength } from '../common/strings'; import { isPromise } from '../common/predicates'; import { is, parse } from '../common/hof'; import { trace } from '../common/trace'; import { services } from '../common/coreservices'; import { Rejection } from './rejectFactory'; import { TargetState } from '../state/targetState'; var defaultOptions = { current: noop, transition: null, traceData: {}, bind: null, }; var TransitionHook = /** @class */ (function () { function TransitionHook(transition, stateContext, registeredHook, options) { var _this = this; this.transition = transition; this.stateContext = stateContext; this.registeredHook = registeredHook; this.options = options; this.isSuperseded = function () { return _this.type.hookPhase === TransitionHookPhase.RUN && !_this.options.transition.isActive(); }; this.options = defaults(options, defaultOptions); this.type = registeredHook.eventType; } /** * Chains together an array of TransitionHooks. * * Given a list of [[TransitionHook]] objects, chains them together. * Each hook is invoked after the previous one completes. * * #### Example: * ```js * var hooks: TransitionHook[] = getHooks(); * let promise: Promise<any> = TransitionHook.chain(hooks); * * promise.then(handleSuccess, handleError); * ``` * * @param hooks the list of hooks to chain together * @param waitFor if provided, the chain is `.then()`'ed off this promise * @returns a `Promise` for sequentially invoking the hooks (in order) */ TransitionHook.chain = function (hooks, waitFor) { // Chain the next hook off the previous var createHookChainR = function (prev, nextHook) { return prev.then(function () { return nextHook.invokeHook(); }); }; return hooks.reduce(createHookChainR, waitFor || services.$q.when()); }; /** * Invokes all the provided TransitionHooks, in order. * Each hook's return value is checked. * If any hook returns a promise, then the rest of the hooks are chained off that promise, and the promise is returned. * If no hook returns a promise, then all hooks are processed synchronously. * * @param hooks the list of TransitionHooks to invoke * @param doneCallback a callback that is invoked after all the hooks have successfully completed * * @returns a promise for the async result, or the result of the callback */ TransitionHook.invokeHooks = function (hooks, doneCallback) { for (var idx = 0; idx < hooks.length; idx++) { var hookResult = hooks[idx].invokeHook(); if (isPromise(hookResult)) { var remainingHooks = hooks.slice(idx + 1); return TransitionHook.chain(remainingHooks, hookResult).then(doneCallback); } } return doneCallback(); }; /** * Run all TransitionHooks, ignoring their return value. */ TransitionHook.runAllHooks = function (hooks) { hooks.forEach(function (hook) { return hook.invokeHook(); }); }; TransitionHook.prototype.logError = function (err) { this.transition.router.stateService.defaultErrorHandler()(err); }; TransitionHook.prototype.invokeHook = function () { var _this = this; var hook = this.registeredHook; if (hook._deregistered) return; var notCurrent = this.getNotCurrentRejection(); if (notCurrent) return notCurrent; var options = this.options; trace.traceHookInvocation(this, this.transition, options); var invokeCallback = function () { return hook.callback.call(options.bind, _this.transition, _this.stateContext); }; var normalizeErr = function (err) { return Rejection.normalize(err).toPromise(); }; var handleError = function (err) { return hook.eventType.getErrorHandler(_this)(err); }; var handleResult = function (result) { return hook.eventType.getResultHandler(_this)(result); }; try { var result = invokeCallback(); if (!this.type.synchronous && isPromise(result)) { return result.catch(normalizeErr).then(handleResult, handleError); } else { return handleResult(result); } } catch (err) { // If callback throws (synchronously) return handleError(Rejection.normalize(err)); } finally { if (hook.invokeLimit && ++hook.invokeCount >= hook.invokeLimit) { hook.deregister(); } } }; /** * This method handles the return value of a Transition Hook. * * A hook can return false (cancel), a TargetState (redirect), * or a promise (which may later resolve to false or a redirect) * * This also handles "transition superseded" -- when a new transition * was started while the hook was still running */ TransitionHook.prototype.handleHookResult = function (result) { var _this = this; var notCurrent = this.getNotCurrentRejection(); if (notCurrent) return notCurrent; // Hook returned a promise if (isPromise(result)) { // Wait for the promise, then reprocess with the resulting value return result.then(function (val) { return _this.handleHookResult(val); }); } trace.traceHookResult(result, this.transition, this.options); // Hook returned false if (result === false) { // Abort this Transition return Rejection.aborted('Hook aborted transition').toPromise(); } var isTargetState = is(TargetState); // hook returned a TargetState if (isTargetState(result)) { // Halt the current Transition and redirect (a new Transition) to the TargetState. return Rejection.redirected(result).toPromise(); } }; /** * Return a Rejection promise if the transition is no longer current due * to a stopped router (disposed), or a new transition has started and superseded this one. */ TransitionHook.prototype.getNotCurrentRejection = function () { var router = this.transition.router; // The router is stopped if (router._disposed) { 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()) { // Abort this transition return Rejection.superseded(this.options.current()).toPromise(); } }; TransitionHook.prototype.toString = function () { var _a = this, options = _a.options, registeredHook = _a.registeredHook; var event = parse('traceData.hookType')(options) || 'internal', context = parse('traceData.context.state.name')(options) || parse('traceData.context')(options) || 'unknown', name = fnToString(registeredHook.callback); return event + " context: " + context + ", " + maxLength(200, name); }; /** * These GetResultHandler(s) are used by [[invokeHook]] below * Each HookType chooses a GetResultHandler (See: [[TransitionService._defineCoreEvents]]) */ TransitionHook.HANDLE_RESULT = function (hook) { return function (result) { return hook.handleHookResult(result); }; }; /** * If the result is a promise rejection, log it. * Otherwise, ignore the result. */ TransitionHook.LOG_REJECTED_RESULT = function (hook) { return function (result) { isPromise(result) && result.catch(function (err) { return hook.logError(Rejection.normalize(err)); }); return undefined; }; }; /** * These GetErrorHandler(s) are used by [[invokeHook]] below * Each HookType chooses a GetErrorHandler (See: [[TransitionService._defineCoreEvents]]) */ TransitionHook.LOG_ERROR = function (hook) { return function (error) { return hook.logError(error); }; }; TransitionHook.REJECT_ERROR = function (hook) { return function (error) { return silentRejection(error); }; }; TransitionHook.THROW_ERROR = function (hook) { return function (error) { throw error; }; }; return TransitionHook; }()); export { TransitionHook }; //# sourceMappingURL=transitionHook.js.map