@uirouter/core
Version:
UI-Router Core: Framework agnostic, State-based routing for JavaScript Single Page Apps
197 lines • 8.8 kB
JavaScript
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