UNPKG

ui-router

Version:

State-based routing for Javascript

690 lines (659 loc) 26.6 kB
/** @module transition */ /** for typedoc */ import {StateDeclaration} from "../state/interface"; import {IInjectable, Predicate} from "../common/common"; import {Transition} from "./module"; import {State, TargetState} from "../state/module"; import {Node} from "../path/module"; /** * The TransitionOptions object can be used to change the behavior of a transition. * * It is passed as the third argument to [[$state.go]], [[$state.transitionTo]], and * can be used with the [[ui-sref-opts]] directive. */ export interface TransitionOptions { /** * This option changes how the Transition interacts with the browser's location bar (URL). * * - If `true`, it will update the url in the location bar. * - If `false`, it will not update the url in the location bar. * - If it is the string "`replace`", it will update the url and also replace the last history record. * * @default `true` */ location ?: (boolean|string); /** * When transitioning to relative path (e.g '`^`'), this option defines which state to be relative from. * @default `$state.current` */ relative ?: (string|StateDeclaration|State); /** * This option sets whether or not the transition's parameter values should be inherited from * the current state parameters. * * - If `true`, it will inherit parameters from current state. * - If `false`, only the parameters which are provided to `transitionTo` will be used. * * @default `false` */ inherit ?: boolean; /** * @deprecated */ notify ?: boolean; /** * This option may be used to force states which are currently active to reload. * * During a normal transition, a state is "retained" if: * - It was previously active * - The state's parameter values have not changed * - All the parent states' parameter values have not changed * * Forcing a reload of a state will cause it to be exited and entered, which will: * - Refetch that state's resolve data * - Exit the state (onExit hook) * - Re-enter the state (onEnter hook) * - Re-render the views (controllers and templates) * * - When `true`, the destination state (and all parent states) will be reloaded. * - When it is a string and is the name of a state, or when it is a State object, * that state and any children states will be reloaded. * * @default `false` */ reload ?: (boolean|string|StateDeclaration|State); /** * You can define your own Transition Options inside this property and use them, e.g., from a Transition Hook */ custom ?: any; /** @internal */ reloadState ?: (State); /** @internal */ previous ?: Transition; /** @internal */ current ?: () => Transition; } /** @internal */ export interface TransitionHookOptions { async ?: boolean; rejectIfSuperseded ?: boolean; current ?: () => Transition; //path? transition ?: Transition; hookType ?: string; target ?: any; traceData ?: any; bind ?: any; } /** * TreeChanges encapsulates the various Paths that are involved in a Transition. * * A UI-Router Transition is from one Path in a State Tree to another Path. For a given Transition, * this object stores the "to" and "from" paths, as well as subsets of those: the "retained", * "exiting" and "entering" paths. * * Each path in TreeChanges is an array of [[Node]] objects. Each Node in the array corresponds to a portion * of a nested state. * * For example, if you had a nested state named `foo.bar.baz`, it would have three * portions, `foo, bar, baz`. If you transitioned **to** `foo.bar.baz` and inspected the TreeChanges.to * Path, you would find a node in the array for each portion: `foo`, `bar`, and `baz`. * * --- * * @todo show visual state tree */ export interface TreeChanges { /** @nodoc */ [key: string]: Node[]; /** The path of nodes in the state tree that the transition is coming *from* */ from: Node[]; /** The path of nodes in the state tree that the transition is going *to* */ to: Node[]; /** * The path of active nodes that the transition is retaining. These nodes are neither exited, nor entered. * Before and after the transition is successful, these nodes are active. */ retained: Node[]; /** * The path of nodes that the transition is exiting. After the Transition is successful, these nodes are no longer active. * * Note that a state that is being reloaded (due to parameter values changing, or `reload: true`) may be in both the * `exiting` and `entering` paths. */ exiting: Node[]; /** * The path of nodes that the transition is entering. After the Transition is successful, these nodes will be active. * Because they are entering, they have their resolves fetched, onEnter hooks run, and their views * (controller+templates) refreshed. * * Note that a state that is reloaded (due to parameter values changing, or `reload: true`) may be in both the * `exiting` and `entering` paths. */ entering: Node[]; } export type IErrorHandler = (error: Error) => void; export interface ITransitionService extends IHookRegistry { create: (fromPath: Node[], targetState: TargetState) => Transition; defaultErrorHandler: (handler?: IErrorHandler) => IErrorHandler; } export type IHookGetter = (hookName: string) => IEventHook[]; export type IHookRegistration = (matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions) => Function; /** * These options may be provided when registering a Transition Hook (such as `onStart`) */ export interface HookRegOptions { /** * Sets the priority of the registered hook * * Hooks of the same type (onBefore, onStart, etc) are invoked in priority order. A hook with a higher priority * is invoked before a hook with a lower priority. * * The default hook priority is 0 */ priority?: number; /** * Specifies what `this` is bound to during hook invocation. */ bind?: any; } /** * This interface specifies the api for registering Transition Hooks. Both the * [[TransitionService]] and also the [[Transition]] object itself implement this interface. * Note: the Transition object only allows hooks to be registered before the Transition is started. */ export interface IHookRegistry { /** * Registers a callback function as an `onBefore` Transition Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * * ### Lifecycle * * `onBefore` hooks are injected and invoked *before* a Transition starts. No resolves have been fetched yet. * Each `onBefore` hook is invoked synchronously, in priority order, and are typically invoked in the same call * stack as [[StateService.transitionTo]]. * * During the `onBefore` phase, the [[Transition]] additional hooks can be registered "on-the-fly" using, for example, * `$transition$.onStart()` or `$transition$.onFinish()`. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before entering the [[onStart]] phase. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * If any hook modifies the transition synchronously (by returning `false` or a [[TargetState]]), the remainder * of the hooks do not get invoked. If any hook returns a promise, the remainder of the `onBefore` hooks are still * invoked. Any promises are then handled asynchronously, during the `onStart` phase of the Transition. * * ### Examples * * #### Default Substate * * This example redirects any transition from 'home' to 'home.dashboard'. This is commonly referred to as a * "default substate". * @example * ``` * * $transitions.onBefore({ to: 'home' }, function($state) { * return $state.target("home.dashboard"); * }); * ``` * * * #### Data Driven Default Substate * * This example provides data-driven default substate functionality. It matches on a transition to any state * which has `defaultSubstate: "some.sub.state"` defined. See: [[Transition.to]] which returns the "to state" * definition. * * @example * ``` * * // state declaration * { * name: 'home', * template: '<div ui-view/>', * defaultSubstate: 'home.dashboard' * } * * var criteria = { * to: function(state) { * return state.defaultSubstate != null; * } * } * $transitions.onBefore(criteria, function($transition$, $state) { * return $state.target($transition$.to().defaultSubstate); * }); * ``` * * * #### Require authentication * * This example cancels a transition to a state which requires authentication, if the user is * not currently authenticated. * * This example assumes a state tree where all states which require authentication are children of * a parent `'auth'` state. This example assumes `MyAuthService` synchronously returns a boolean from * `isAuthenticated()`. * @example * ``` * * $transitions.onBefore( { to: 'auth.*', from: '*' }, function(MyAuthService) { * // If isAuthenticated is false, the transition is cancelled. * return MyAuthService.isAuthenticated(); * }); * ``` * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onBefore(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onStart` Transition Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * * ### Lifecycle * * `onStart` hooks are invoked asynchronously, in priority order, when the Transition starts running. * At this point, the Transition has not exited nor entered any states yet. * * Note: a high priority `onStart` hook will fetch any Eager Resolves in the "to path", which were not already fetched. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * Any resolves which the target state has access to may be injected. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before invoking the next hook. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * ### Example * * #### Login during transition * * This example intercepts any transition to a state which requires authentication, when the user is * not currently authenticated. It allows the user to authenticate asynchronously, then resumes the * transition. If the user did not authenticate successfully, it redirects to the "guest" state, which * does not require authentication. * * This example assumes: * - a state tree where all states which require authentication are children of a parent `'auth'` state. * - `MyAuthService.isAuthenticated()` synchronously returns a boolean. * - `MyAuthService.authenticate()` presents a login dialog, and returns a promise which is resolved * or rejected, whether or not the login attempt was successful. * * @example * ``` * * $transitions.onStart( { to: 'auth.*' }, function(MyAuthService, $state) { * * // If the user is not authenticated * if (!MyAuthService.isAuthenticated()) { * * // Then return a promise for a successful login. * // The transition will wait for this promise to settle * * return MyAuthService.authenticate().catch(function() { * * // Redirect to a state that we know doesn't require auth. * return $state.target("guest"); * }); * } * }); * ``` * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onStart(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onEnter` Transition+State Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * Note: for `onEnter` hooks, the `to` in the `matchCriteria` matches the entering state, not the Transition "to state". * * ### Lifecycle * * `onEnter` hooks are invoked asynchronously, in priority order, when the Transition is entering a state. States * are entered after the `onRetain` hooks. * * Note: a high priority `onEnter` hook fetches any Lazy Resolves defined for the state being entered. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * Any resolves which the entering state has access to may be injected. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before invoking the next hook. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * ### Inside a state declaration * * State hooks can be registered using `$transitions` ([[TransitionService]]), as an `onBefore` "on-the-fly" registration * using `$transition$` (the [[Transition]] instance), or as part of a state declaration. * * * ### Examples * * #### Audit Log * * This example uses a service to log that a user has entered the admin section of an app. * This assumes that there are substates of the "admin" state, such as "admin.users", "admin.pages", etc. * @example * ``` * * $transitions.onEnter({ to: 'admin' }, function(AuditService, $state$, $transition$) { * AuditService.log("Entered admin module while transitioning to " + $transition$.to().name); * } * ``` * * #### Audit Log (inside a state declaration) * * The `onEnter` inside this state declaration is syntactic sugar for the previous Audit Log example. * ``` * { * name: 'admin', * template: '<div ui-view/>', * onEnter: function(AuditService, $state$, $transition$) { * AuditService.log("Entered admin module while transitioning to " + $transition$.to().name); * } * } * ``` * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onEnter(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onRetain` Transition+State Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * Note: for `onRetain` hooks, the `to` in the `matchCriteria` matches the retained state, not the Transition "to state". * * ### Lifecycle * * `onRetain` hooks are invoked asynchronously, in priority order, after `onExit` hooks. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * Any resolves which the retained state has access to may be injected. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before invoking the next hook. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * ### Inside a state declaration * * State hooks can be registered using `$transitions` ([[TransitionService]]), as an `onBefore` "on-the-fly" registration * using `$transition$` (the [[Transition]] instance), or as part of a state declaration. * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onRetain(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onExit` Transition+State Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * Note: for `onExit` hooks, the `from` in the `matchCriteria` matches the exiting state, not the Transition "from state". * * ### Lifecycle * * `onExit` hooks are invoked asynchronously, in priority order, when the Transition is exiting a state. States * are exited after the Transition's `onStart` phase is complete. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * Any resolves which the exiting state has access to may be injected. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before invoking the next hook. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * ### Inside a state declaration * * State hooks can be registered using `$transitions` ([[TransitionService]]), as an `onBefore` "on-the-fly" registration * using `$transition$` (the [[Transition]] instance), or as part of a state declaration. * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onExit(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onFinish` Transition Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * * ### Lifecycle * * `onFinish` hooks are invoked asynchronously, in priority order, just before the Transition completes, after * all states are entered and exited. * * ### Special injectables * * The current [[Transition]] can be injected as `$transition$`. * * Any resolves which the "to state" has access to may be injected. * * ### Return value * * The hook's return value can be used to modify the current Transition: * - `false`: this will abort the current transition * - a [[TargetState]]: The Transition will be redirected to the new target state (created from the * [[StateService.target]] factory). * - A promise: The Transition waits for this promise to settle before invoking the next hook. * - As above, the promise's _resolved value_ may be `false` or a [[TargetState]] * - If the promise is _rejected_, the Transition is aborted. The promise's rejection reason is used to reject * the overall Transition promise. * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onFinish(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onSuccess` Transition Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * * ### Lifecycle * * `onSuccess` hooks are chained off the Transition's promise. If the Transition is successful, the promise * is resolved, and the `onSuccess` hooks are then invoked. * * ### Special injectables * * The successful [[Transition]] can be injected as `$transition$`. * * Any previously fetched resolves which the "to state" has access to may be injected. These hooks will not * trigger/wait for any unfetched resolves to fetch. * * ### Return value * * Since the Transition is already completed, the hook's return value is ignored * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onSuccess(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Registers a callback function as an `onError` Transition Hook. * * The `matchCriteria` is used to determine which Transitions the hook should be invoked during. * * ### Lifecycle * * `onError` hooks are chained off the Transition's promise. If the Transition fails, the promise * is rejected, and the `onError` hooks are then invoked. * * ### Special injectables * * The failed [[Transition]] can be injected as `$transition$`. * * The transition rejection reason can be injected as `$error$` * * ### Return value * * Since the Transition is already completed, the hook's return value is ignored * * @param matchCriteria defines which Transitions the Hook should be invoked for. * @param callback the hook function which will be injected and invoked. * @returns a function which deregisters the hook. */ onError(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function; /** * Returns all the registered hooks of a given `hookName` type * * @example * ``` * * $transitions.getHooks("onEnter") * ``` */ getHooks(hookName: string): IEventHook[]; } /** A predicate type which takes a [[State]] and returns a boolean */ export type IStateMatch = Predicate<State> /** * This object is used to configure whether or not a Transition Hook is invoked for a particular transition, * based on the Transition's "to state" and "from state". * * Each property (`to`, `from`, `exiting`, `retained`, and `entering`) can be state globs, a function that takes a * state, or a boolean (see [[HookMatchCriterion]]) * * All properties are optional. If any property is omitted, it is replaced with the value `true`, and always matches. * * @example * ```js * * // This matches a transition coming from the `parent` state and going to the `parent.child` state. * var match = { * to: 'parent', * from: 'parent.child' * } * ``` * * @example * ```js * * // This matches a transition coming from any substate of `parent` and going directly to the `parent` state. * var match = { * to: 'parent', * from: 'parent.**' * } * ``` * * @example * ```js * * // This matches a transition coming from any state and going to any substate of `mymodule` * var match = { * to: 'mymodule.**' * } * ``` * * @example * ```js * * // This matches a transition coming from any state and going to any state that has `data.authRequired` * // set to a truthy value. * var match = { * to: function(state) { * return state.data != null && state.data.authRequired === true; * } * } * ``` * * @example * ```js * * // This matches a transition that is exiting `parent.child` * var match = { * exiting: 'parent.child' * } * ``` */ export interface HookMatchCriteria { /** A [[HookMatchCriterion]] to match the destination state */ to?: HookMatchCriterion; /** A [[HookMatchCriterion]] to match the original (from) state */ from?: HookMatchCriterion; /** A [[HookMatchCriterion]] to match any state that would be exiting */ exiting?: HookMatchCriterion; /** A [[HookMatchCriterion]] to match any state that would be retained */ retained?: HookMatchCriterion; /** A [[HookMatchCriterion]] to match any state that would be entering */ entering?: HookMatchCriterion; } export interface IMatchingNodes { to: Node[]; from: Node[]; exiting: Node[]; retained: Node[]; entering: Node[]; } /** * A glob string that matches the name of a state * Or, a function with the signature `function(state) { return matches; }` * which should return a boolean to indicate if a state matches. * Or, `true` to match anything */ export type HookMatchCriterion = (string|IStateMatch|boolean) export interface IEventHook { callback: IInjectable; priority: number; bind: any; matches: (treeChanges: TreeChanges) => IMatchingNodes; }