diff --git a/src/common/common.ts b/src/common/common.ts index 357538ec..715e798d 100644 --- a/src/common/common.ts +++ b/src/common/common.ts @@ -32,6 +32,7 @@ export interface TypedMap { [key: string]: T; } export type Predicate = (x?: X) => boolean; +export type PredicateBinary = (x?: X, y?: Y) => boolean; /** * An ng1-style injectable * diff --git a/src/transition/hookBuilder.ts b/src/transition/hookBuilder.ts index 03f3f8c2..f7041814 100644 --- a/src/transition/hookBuilder.ts +++ b/src/transition/hookBuilder.ts @@ -60,7 +60,7 @@ export class HookBuilder { const treeChanges = transition.treeChanges(); // Find all the matching registered hooks for a given hook type - const matchingHooks = this.getMatchingHooks(hookType, treeChanges); + const matchingHooks = this.getMatchingHooks(hookType, treeChanges, transition); if (!matchingHooks) return []; const baseHookOptions = { @@ -70,7 +70,7 @@ export class HookBuilder { const makeTransitionHooks = (hook: RegisteredHook) => { // Fetch the Nodes that caused this hook to match. - const matches: IMatchingNodes = hook.matches(treeChanges); + const matches: IMatchingNodes = hook.matches(treeChanges, transition); // Select the PathNode[] that will be used as TransitionHook context objects const matchingNodes: PathNode[] = matches[hookType.criteriaMatchPath.name]; @@ -108,7 +108,11 @@ export class HookBuilder { * * @returns an array of matched [[RegisteredHook]]s */ - public getMatchingHooks(hookType: TransitionEventType, treeChanges: TreeChanges): RegisteredHook[] { + public getMatchingHooks( + hookType: TransitionEventType, + treeChanges: TreeChanges, + transition: Transition + ): RegisteredHook[] { const isCreate = hookType.hookPhase === TransitionHookPhase.CREATE; // Instance and Global hook registries @@ -119,7 +123,7 @@ export class HookBuilder { .map((reg: IHookRegistry) => reg.getHooks(hookType.name)) // Get named hooks from registries .filter(assertPredicate(isArray, `broken event named: ${hookType.name}`)) // Sanity check .reduce(unnestR, []) // Un-nest RegisteredHook[][] to RegisteredHook[] array - .filter(hook => hook.matches(treeChanges)); // Only those satisfying matchCriteria + .filter(hook => hook.matches(treeChanges, transition)); // Only those satisfying matchCriteria } } diff --git a/src/transition/hookRegistry.ts b/src/transition/hookRegistry.ts index 7830a339..c603088e 100644 --- a/src/transition/hookRegistry.ts +++ b/src/transition/hookRegistry.ts @@ -18,6 +18,7 @@ import { IMatchingNodes, HookFn, } from './interface'; +import { Transition } from './transition'; import { StateObject } from '../state/stateObject'; import { TransitionEventType } from './transitionEventType'; import { TransitionService } from './transitionService'; @@ -35,7 +36,7 @@ import { TransitionService } from './transitionService'; * - If a function, matchState calls the function with the state and returns true if the function's result is truthy. * @returns {boolean} */ -export function matchState(state: StateObject, criterion: HookMatchCriterion) { +export function matchState(state: StateObject, criterion: HookMatchCriterion, transition: Transition) { const toMatch = isString(criterion) ? [criterion] : criterion; function matchGlobs(_state: StateObject) { @@ -51,7 +52,7 @@ export function matchState(state: StateObject, criterion: HookMatchCriterion) { } const matchFn = (isFunction(toMatch) ? toMatch : matchGlobs); - return !!matchFn(state); + return !!matchFn(state, transition); } /** @@ -93,9 +94,9 @@ export class RegisteredHook { * with `entering: (state) => true` which only matches when a state is actually * being entered. */ - private _matchingNodes(nodes: PathNode[], criterion: HookMatchCriterion): PathNode[] { + private _matchingNodes(nodes: PathNode[], criterion: HookMatchCriterion, transition: Transition): PathNode[] { if (criterion === true) return nodes; - const matching = nodes.filter(node => matchState(node.state, criterion)); + const matching = nodes.filter(node => matchState(node.state, criterion, transition)); return matching.length ? matching : null; } @@ -132,7 +133,7 @@ export class RegisteredHook { * }; * ``` */ - private _getMatchingNodes(treeChanges: TreeChanges): IMatchingNodes { + private _getMatchingNodes(treeChanges: TreeChanges, transition: Transition): IMatchingNodes { const criteria = extend(this._getDefaultMatchCriteria(), this.matchCriteria); const paths: PathType[] = values(this.tranSvc._pluginapi._getPathTypes()); @@ -144,7 +145,7 @@ export class RegisteredHook { const path = treeChanges[pathtype.name] || []; const nodes: PathNode[] = isStateHook ? path : [tail(path)]; - mn[pathtype.name] = this._matchingNodes(nodes, criteria[pathtype.name]); + mn[pathtype.name] = this._matchingNodes(nodes, criteria[pathtype.name], transition); return mn; }, {} as IMatchingNodes @@ -157,8 +158,8 @@ export class RegisteredHook { * @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values * are the matching [[PathNode]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering) */ - matches(treeChanges: TreeChanges): IMatchingNodes { - const matches = this._getMatchingNodes(treeChanges); + matches(treeChanges: TreeChanges, transition: Transition): IMatchingNodes { + const matches = this._getMatchingNodes(treeChanges, transition); // Check if all the criteria matched the TreeChanges object const allMatched = values(matches).every(identity); diff --git a/src/transition/interface.ts b/src/transition/interface.ts index fa0a7bdd..315252e2 100644 --- a/src/transition/interface.ts +++ b/src/transition/interface.ts @@ -1,6 +1,6 @@ /** @publicapi @module transition */ /** */ import { StateDeclaration } from '../state/interface'; -import { Predicate } from '../common/common'; +import { PredicateBinary } from '../common/common'; import { Transition } from './transition'; import { StateObject } from '../state/stateObject'; @@ -716,8 +716,8 @@ export interface IHookRegistry { getHooks(hookName: string): RegisteredHook[]; } -/** A predicate type which tests if a [[StateObject]] passes some test. Returns a boolean. */ -export type IStateMatch = Predicate; +/** A predicate type which tests if a [[StateObject]] and [[Transition]] passes some test. Returns a boolean. */ +export type IStateMatch = PredicateBinary; /** * This object is used to configure whether or not a Transition Hook is invoked for a particular transition, @@ -765,6 +765,14 @@ export type IStateMatch = Predicate; * } * } * ``` + * #### Example: + * ```js + * // This will match when route is just entered (initial load) or when the state is hard-refreshed + * // by specifying `{refresh: true}` as transition options. + * var match = { + * from: (state, transition) => state.self.name === '' || transition.options().reload + * } + * ``` * * #### Example: * ```js @@ -826,7 +834,7 @@ export interface PathType { * * A [[Glob]] string that matches the name of a state. * - * Or, a function with the signature `function(state) { return matches; }` + * Or, a function with the signature `function(state, transition) { return matches; }` * which should return a boolean to indicate if a state matches. * * Or, `true` to always match diff --git a/src/transition/transition.ts b/src/transition/transition.ts index bbef0ad1..b859d7f8 100644 --- a/src/transition/transition.ts +++ b/src/transition/transition.ts @@ -247,8 +247,8 @@ export class Transition implements IHookRegistry { return this.is({ to: compare.$to().name, from: compare.$from().name }); } return !( - (compare.to && !matchState(this.$to(), compare.to)) || - (compare.from && !matchState(this.$from(), compare.from)) + (compare.to && !matchState(this.$to(), compare.to, this)) || + (compare.from && !matchState(this.$from(), compare.from, this)) ); }