All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.router.transition.transition-service.js Maven / Gradle / Ivy

import { TransitionHookScope, TransitionHookPhase } from "./interface";
import { Transition } from "./transition";
import { makeEvent } from "./hook-registry";
import {
  registerAddCoreResolvables,
  treeChangesCleanup,
} from "../hooks/core-resolvables";
import {
  registerOnExitHook,
  registerOnRetainHook,
  registerOnEnterHook,
} from "../hooks/on-enter-exit-retain";
import {
  registerEagerResolvePath,
  registerLazyResolveState,
  registerResolveRemaining,
} from "../hooks/resolve";
import {
  registerActivateViews,
  registerLoadEnteringViews,
} from "../hooks/views";
import { registerUpdateGlobalState } from "../hooks/update-globals";

import { registerLazyLoadHook } from "../hooks/lazy-load";
import { TransitionEventType } from "./transition-event-type";
import { TransitionHook } from "./transition-hook";
import { isDefined } from "../../shared/utils";
import { createProxyFunctions } from "../../shared/common";
import { val } from "../../shared/hof";
import { registerIgnoredTransitionHook } from "../hooks/ignored-transition";
import { registerInvalidTransitionHook } from "../hooks/invalid-transition";
import { registerRedirectToHook } from "../hooks/redirect-to";
import { registerUpdateUrl } from "../hooks/url";
/**
 * The default [[Transition]] options.
 *
 * Include this object when applying custom defaults:
 * let reloadOpts = { reload: true, notify: true }
 * let options = defaults(theirOpts, customDefaults, defaultOptions);
 */
export let defaultTransOpts = {
  location: true,
  relative: null,
  inherit: false,
  notify: true,
  reload: false,
  supercede: true,
  custom: {},
  current: () => null,
  source: "unknown",
};
/**
 * This class provides services related to Transitions.
 *
 * - Most importantly, it allows global Transition Hooks to be registered.
 * - It allows the default transition error handler to be set.
 * - It also has a factory function for creating new [[Transition]] objects, (used internally by the [[StateService]]).
 *
 * At bootstrap, [[UIRouter]] creates a single instance (singleton) of this class.
 *
 * This API is located at `router.transitionService` ([[UIRouter.transitionService]])
 */
export class TransitionProvider {
  static $inject = ["$routerGlobalsProvider", "$viewProvider"];

  /**
   * @param {import('../globals').UIRouterGlobals} globals
   */
  constructor(globals, viewService) {
    this._transitionCount = 0;
    /** The transition hook types, such as `onEnter`, `onStart`, etc */
    this._eventTypes = [];
    /** @internal The registered transition hooks */
    this._registeredHooks = {};
    /** The  paths on a criteria object */
    this._criteriaPaths = {};
    this.globals = globals;
    this.$view = viewService;
    this._deregisterHookFns = {};
    this._pluginapi = createProxyFunctions(val(this), {}, val(this), [
      "_definePathType",
      "_defineEvent",
      "_getPathTypes",
      "_getEvents",
      "getHooks",
    ]);
    this._defineCorePaths();
    this._defineCoreEvents();
    this._registerCoreTransitionHooks();
    globals.successfulTransitions.onEvict(treeChangesCleanup);
  }

  $get = [
    "$state",
    "$urlService",
    "$stateRegistry",
    "$view",
    (stateService, urlService, stateRegistry, viewService) => {
      // Lazy load state trees
      this._deregisterHookFns.lazyLoad = registerLazyLoadHook(
        this,
        stateService,
        urlService,
        stateRegistry,
      );

      // After globals.current is updated at priority: 10000
      this._deregisterHookFns.updateUrl = registerUpdateUrl(
        this,
        stateService,
        urlService,
      );

      // Wire up redirectTo hook
      this._deregisterHookFns.redirectTo = registerRedirectToHook(
        this,
        stateService,
      );

      this._deregisterHookFns.activateViews = registerActivateViews(
        this,
        viewService,
      );
      return this;
    },
  ];
  /**
   * Registers a [[TransitionHookFn]], called *while a transition is being constructed*.
   *
   * Registers a transition lifecycle hook, which is invoked during transition construction.
   *
   * This low level hook should only be used by plugins.
   * This can be a useful time for plugins to add resolves or mutate the transition as needed.
   * The Sticky States plugin uses this hook to modify the treechanges.
   *
   * ### Lifecycle
   *
   * `onCreate` hooks are invoked *while a transition is being constructed*.
   *
   * ### Return value
   *
   * The hook's return value is ignored
   *
   * @internal
   * @param criteria defines which Transitions the Hook should be invoked for.
   * @param callback the hook function which will be invoked.
   * @param options the registration options
   * @returns a function which deregisters the hook.
   */

  /**
   * Creates a new [[Transition]] object
   *
   * This is a factory function for creating new Transition objects.
   * It is used internally by the [[StateService]] and should generally not be called by application code.
   *
   * @internal
   * @param fromPath the path to the current state (the from state)
   * @param targetState the target state (destination)
   * @returns a Transition
   */
  create(fromPath, targetState) {
    return new Transition(fromPath, targetState, this, this.globals);
  }

  _defineCoreEvents() {
    const Phase = TransitionHookPhase;
    const TH = TransitionHook;
    const paths = this._criteriaPaths;
    const NORMAL_SORT = false,
      REVERSE_SORT = true;
    const SYNCHRONOUS = true;
    this._defineEvent(
      "onCreate",
      Phase.CREATE,
      0,
      paths.to,
      NORMAL_SORT,
      TH.LOG_REJECTED_RESULT,
      TH.THROW_ERROR,
      SYNCHRONOUS,
    );
    this._defineEvent("onBefore", Phase.BEFORE, 0, paths.to);
    this._defineEvent("onStart", Phase.RUN, 0, paths.to);
    this._defineEvent("onExit", Phase.RUN, 100, paths.exiting, REVERSE_SORT);
    this._defineEvent("onRetain", Phase.RUN, 200, paths.retained);
    this._defineEvent("onEnter", Phase.RUN, 300, paths.entering);
    this._defineEvent("onFinish", Phase.RUN, 400, paths.to);
    this._defineEvent(
      "onSuccess",
      Phase.SUCCESS,
      0,
      paths.to,
      NORMAL_SORT,
      TH.LOG_REJECTED_RESULT,
      TH.LOG_ERROR,
      SYNCHRONOUS,
    );
    this._defineEvent(
      "onError",
      Phase.ERROR,
      0,
      paths.to,
      NORMAL_SORT,
      TH.LOG_REJECTED_RESULT,
      TH.LOG_ERROR,
      SYNCHRONOUS,
    );
  }

  _defineCorePaths() {
    const { STATE, TRANSITION } = TransitionHookScope;
    this._definePathType("to", TRANSITION);
    this._definePathType("from", TRANSITION);
    this._definePathType("exiting", STATE);
    this._definePathType("retained", STATE);
    this._definePathType("entering", STATE);
  }

  _defineEvent(
    name,
    hookPhase,
    hookOrder,
    criteriaMatchPath,
    reverseSort = false,
    getResultHandler = TransitionHook.HANDLE_RESULT,
    getErrorHandler = TransitionHook.REJECT_ERROR,
    synchronous = false,
  ) {
    const eventType = new TransitionEventType(
      name,
      hookPhase,
      hookOrder,
      criteriaMatchPath,
      reverseSort,
      getResultHandler,
      getErrorHandler,
      synchronous,
    );
    this._eventTypes.push(eventType);
    makeEvent(this, this, eventType);
  }

  _getEvents(phase) {
    const transitionHookTypes = isDefined(phase)
      ? this._eventTypes.filter((type) => type.hookPhase === phase)
      : this._eventTypes.slice();
    return transitionHookTypes.sort((l, r) => {
      const cmpByPhase = l.hookPhase - r.hookPhase;
      return cmpByPhase === 0 ? l.hookOrder - r.hookOrder : cmpByPhase;
    });
  }
  /**
   * Adds a Path to be used as a criterion against a TreeChanges path
   *
   * For example: the `exiting` path in [[HookMatchCriteria]] is a STATE scoped path.
   * It was defined by calling `defineTreeChangesCriterion('exiting', TransitionHookScope.STATE)`
   * Each state in the exiting path is checked against the criteria and returned as part of the match.
   *
   * Another example: the `to` path in [[HookMatchCriteria]] is a TRANSITION scoped path.
   * It was defined by calling `defineTreeChangesCriterion('to', TransitionHookScope.TRANSITION)`
   * Only the tail of the `to` path is checked against the criteria and returned as part of the match.
   *
   * @internal
   */
  _definePathType(name, hookScope) {
    this._criteriaPaths[name] = { name, scope: hookScope };
  }

  _getPathTypes() {
    return this._criteriaPaths;
  }

  getHooks(hookName) {
    return this._registeredHooks[hookName];
  }

  _registerCoreTransitionHooks() {
    const fns = this._deregisterHookFns;
    fns.addCoreResolves = registerAddCoreResolvables(this);
    fns.ignored = registerIgnoredTransitionHook(this);
    fns.invalid = registerInvalidTransitionHook(this);

    // Wire up onExit/Retain/Enter state hooks
    fns.onExit = registerOnExitHook(this);
    fns.onRetain = registerOnRetainHook(this);
    fns.onEnter = registerOnEnterHook(this);
    // Wire up Resolve hooks
    fns.eagerResolve = registerEagerResolvePath(this);
    fns.lazyResolve = registerLazyResolveState(this);
    fns.resolveAll = registerResolveRemaining(this);
    // Wire up the View management hooks
    fns.loadViews = registerLoadEnteringViews(this);

    // Updates global state after a transition
    fns.updateGlobals = registerUpdateGlobalState(this);
    // Lazy load state trees
    fns.lazyLoad = registerLazyLoadHook(this);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy