Skip to content

Commit 76ab22d

Browse files
fix(ViewHooks): Fix problem with injecting uiCanExit
feat(TransitionHook): Allow a transition hook's `this` to be bound Added `{bind: any}` to the HookRegOptions Closes #2661
1 parent d000919 commit 76ab22d

11 files changed

+233
-43
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"jsdoc": "git://github.com/jsdoc3/jsdoc.git#v3.2.2",
6565
"karma": "~0.12.0",
6666
"karma-chrome-launcher": "~0.1.0",
67-
"karma-jasmine": "~0.3.6",
67+
"karma-jasmine": "^0.3.8",
6868
"karma-phantomjs-launcher": "~0.1.0",
6969
"karma-script-launcher": "~0.1.0",
7070
"karma-systemjs": "^0.7.2",

src/ng1/viewDirective.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {Transition} from "../transition/transition";
1313
import {Node} from "../path/node";
1414
import {Param} from "../params/param";
1515
import {kebobString} from "../common/strings";
16+
import {HookRegOptions} from "../transition/interface";
1617

1718
export type UIViewData = {
1819
$cfg: Ng1ViewConfig;
@@ -384,6 +385,7 @@ function registerControllerCallbacks($transitions: TransitionService, controller
384385
// Call $onInit() ASAP
385386
if (isFunction(controllerInstance.$onInit)) controllerInstance.$onInit();
386387

388+
var hookOptions: HookRegOptions = { bind: controllerInstance };
387389
// Add component-level hook for onParamsChange
388390
if (isFunction(controllerInstance.uiOnParamsChanged)) {
389391
// Fire callback on any successful transition
@@ -412,18 +414,19 @@ function registerControllerCallbacks($transitions: TransitionService, controller
412414
controllerInstance.uiOnParamsChanged(filter(toParams, (val, key) => changedKeys.indexOf(key) !== -1), $transition$);
413415
}
414416
};
415-
$scope.$on('$destroy', $transitions.onSuccess({}, ['$transition$', paramsUpdated]));
417+
$scope.$on('$destroy', $transitions.onSuccess({}, ['$transition$', paramsUpdated]), hookOptions);
416418

417419
// Fire callback on any IGNORED transition
418420
let onDynamic = ($error$, $transition$) => {
419421
if ($error$.type === RejectType.IGNORED) paramsUpdated($transition$);
420422
};
421-
$scope.$on('$destroy', $transitions.onError({}, ['$error$', '$transition$', onDynamic]));
423+
$scope.$on('$destroy', $transitions.onError({}, ['$error$', '$transition$', onDynamic]), hookOptions);
422424
}
423425

424426
// Add component-level hook for uiCanExit
425427
if (isFunction(controllerInstance.uiCanExit)) {
426-
$scope.$on('$destroy', $transitions.onBefore({exiting: cfg.node.state.name}, controllerInstance.uiCanExit.bind(controllerInstance)));
428+
var criteria = {exiting: cfg.node.state.name};
429+
$scope.$on('$destroy', $transitions.onBefore(criteria, controllerInstance.uiCanExit, hookOptions));
427430
}
428431
}
429432

src/ng1/viewsBuilder.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ export function ng1ViewsBuilder(state: State) {
7070
}
7171

7272
// for ng 1.2 style, process the scope: { input: "=foo" } object
73-
const scopeBindings = bindingsObj => Object.keys(bindingsObj)
73+
const scopeBindings = bindingsObj => Object.keys(bindingsObj || {})
7474
.map(key => [key, /^[=<](.*)/.exec(bindingsObj[key])])
7575
.filter(tuple => isDefined(tuple[1]))
7676
.map(tuple => tuple[1][1] || tuple[0]);
7777

7878
// for ng 1.3+ bindToController or 1.5 component style, process a $$bindings object
79-
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj)
79+
const bindToCtrlBindings = bindingsObj => Object.keys(bindingsObj || {})
8080
.filter(key => !!/[=<]/.exec(bindingsObj[key].mode))
8181
.map(key => bindingsObj[key].attrName);
8282

src/resolve/resolveContext.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export class ResolveContext {
164164
let resolvables = this.getResolvablesForFn(fn);
165165
trace.tracePathElementInvoke(tail(this._path), fn, Object.keys(resolvables), extend({when: "Now "}, options));
166166
let resolvedLocals = map(resolvables, prop("data"));
167-
return services.$injector.invoke(<Function> fn, null, extend({}, locals, resolvedLocals));
167+
return services.$injector.invoke(<Function> fn, options.bind || null, extend({}, locals, resolvedLocals));
168168
}
169169
}
170170

src/transition/hookBuilder.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export class HookBuilder {
4848
getOnRetainHooks = () => this._buildNodeHooks("onRetain", "retained", tupleSort(), (node) => ({ $state$: node.state }));
4949
getOnEnterHooks = () => this._buildNodeHooks("onEnter", "entering", tupleSort(), (node) => ({ $state$: node.state }));
5050
getOnFinishHooks = () => this._buildNodeHooks("onFinish", "to", tupleSort(), (node) => ({ $treeChanges$: this.treeChanges }));
51-
getOnSuccessHooks = () => this._buildNodeHooks("onSuccess", "to", tupleSort(), undefined, {async: false, rejectIfSuperseded: false});
52-
getOnErrorHooks = () => this._buildNodeHooks("onError", "to", tupleSort(), undefined, {async: false, rejectIfSuperseded: false});
51+
getOnSuccessHooks = () => this._buildNodeHooks("onSuccess", "to", tupleSort(), undefined, { async: false, rejectIfSuperseded: false });
52+
getOnErrorHooks = () => this._buildNodeHooks("onError", "to", tupleSort(), undefined, { async: false, rejectIfSuperseded: false });
5353

5454
asyncHooks() {
5555
let onStartHooks = this.getOnStartHooks();
@@ -79,7 +79,7 @@ export class HookBuilder {
7979
matchingNodesProp: string,
8080
sortHooksFn: (l: HookTuple, r: HookTuple) => number,
8181
getLocals: (node: Node) => any = (node) => ({}),
82-
options: TransitionHookOptions = {}): TransitionHook[] {
82+
options?: TransitionHookOptions): TransitionHook[] {
8383

8484
// Find all the matching registered hooks for a given hook type
8585
let matchingHooks = this._matchingHooks(hookType, this.treeChanges);
@@ -93,7 +93,7 @@ export class HookBuilder {
9393

9494
// Return an array of HookTuples
9595
return nodes.map(node => {
96-
let _options = extend({ traceData: { hookType, context: node} }, this.baseHookOptions, options);
96+
let _options = extend({ bind: hook.bind, traceData: { hookType, context: node} }, this.baseHookOptions, options);
9797
let transitionHook = new TransitionHook(hook.callback, getLocals(node), node.resolveContext, _options);
9898
return <HookTuple> { hook, node, transitionHook };
9999
});

src/transition/hookRegistry.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {isString, isFunction} from "../common/predicates";
44
import {val} from "../common/hof";
55
import {Node} from "../path/node";
66

7-
import {IMatchCriteria, IStateMatch, IEventHook, IHookRegistry, IHookRegistration, TreeChanges, MatchCriterion, IMatchingNodes} from "./interface";
7+
import {HookRegOptions, HookMatchCriteria, IStateMatch, IEventHook, IHookRegistry, IHookRegistration, TreeChanges, HookMatchCriterion, IMatchingNodes} from "./interface";
88
import {Glob} from "../common/glob";
99
import {State} from "../state/stateObject";
1010

@@ -18,7 +18,7 @@ import {State} from "../state/stateObject";
1818
* - If a function, matchState calls the function with the state and returns true if the function's result is truthy.
1919
* @returns {boolean}
2020
*/
21-
export function matchState(state: State, criterion: MatchCriterion) {
21+
export function matchState(state: State, criterion: HookMatchCriterion) {
2222
let toMatch = isString(criterion) ? [criterion] : criterion;
2323

2424
function matchGlobs(_state) {
@@ -40,16 +40,18 @@ export function matchState(state: State, criterion: MatchCriterion) {
4040

4141
export class EventHook implements IEventHook {
4242
callback: IInjectable;
43-
matchCriteria: IMatchCriteria;
43+
matchCriteria: HookMatchCriteria;
4444
priority: number;
45+
bind: any;
4546

46-
constructor(matchCriteria: IMatchCriteria, callback: IInjectable, options: { priority?: number } = <any>{}) {
47+
constructor(matchCriteria: HookMatchCriteria, callback: IInjectable, options: HookRegOptions = <any>{}) {
4748
this.callback = callback;
4849
this.matchCriteria = extend({ to: true, from: true, exiting: true, retained: true, entering: true }, matchCriteria);
4950
this.priority = options.priority || 0;
51+
this.bind = options.bind || null;
5052
}
5153

52-
private static _matchingNodes(nodes: Node[], criterion: MatchCriterion): Node[] {
54+
private static _matchingNodes(nodes: Node[], criterion: HookMatchCriterion): Node[] {
5355
if (criterion === true) return nodes;
5456
let matching = nodes.filter(node => matchState(node.state, criterion));
5557
return matching.length ? matching : null;
@@ -59,7 +61,7 @@ export class EventHook implements IEventHook {
5961
* Determines if this hook's [[matchCriteria]] match the given [[TreeChanges]]
6062
*
6163
* @returns an IMatchingNodes object, or null. If an IMatchingNodes object is returned, its values
62-
* are the matching [[Node]]s for each [[MatchCriterion]] (to, from, exiting, retained, entering)
64+
* are the matching [[Node]]s for each [[HookMatchCriterion]] (to, from, exiting, retained, entering)
6365
*/
6466
matches(treeChanges: TreeChanges): IMatchingNodes {
6567
let mc = this.matchCriteria, _matchingNodes = EventHook._matchingNodes;

src/transition/interface.ts

+45-22
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export interface TransitionHookOptions {
8888
hookType ?: string;
8989
target ?: any;
9090
traceData ?: any;
91+
bind ?: any;
9192
}
9293

9394
/**
@@ -151,7 +152,27 @@ export interface ITransitionService extends IHookRegistry {
151152
}
152153

153154
export type IHookGetter = (hookName: string) => IEventHook[];
154-
export type IHookRegistration = (matchCriteria: IMatchCriteria, callback: IInjectable, options?) => Function;
155+
export type IHookRegistration = (matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions) => Function;
156+
157+
/**
158+
* These options may be provided when registering a Transition Hook (such as `onStart`)
159+
*/
160+
export interface HookRegOptions {
161+
/**
162+
* Sets the priority of the registered hook
163+
*
164+
* Hooks of the same type (onBefore, onStart, etc) are invoked in priority order. A hook with a higher priority
165+
* is invoked before a hook with a lower priority.
166+
*
167+
* The default hook priority is 0
168+
*/
169+
priority?: number;
170+
171+
/**
172+
* Specifies what `this` is bound to during hook invocation.
173+
*/
174+
bind?: any;
175+
}
155176

156177
/**
157178
* This interface specifies the api for registering Transition Hooks. Both the
@@ -255,7 +276,7 @@ export interface IHookRegistry {
255276
* @param callback the hook function which will be injected and invoked.
256277
* @returns a function which deregisters the hook.
257278
*/
258-
onBefore(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
279+
onBefore(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
259280

260281
/**
261282
* Registers a callback function as an `onStart` Transition Hook.
@@ -325,7 +346,7 @@ export interface IHookRegistry {
325346
* @param callback the hook function which will be injected and invoked.
326347
* @returns a function which deregisters the hook.
327348
*/
328-
onStart(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
349+
onStart(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
329350

330351
/**
331352
* Registers a callback function as an `onEnter` Transition+State Hook.
@@ -394,7 +415,7 @@ export interface IHookRegistry {
394415
* @param callback the hook function which will be injected and invoked.
395416
* @returns a function which deregisters the hook.
396417
*/
397-
onEnter(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
418+
onEnter(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
398419

399420
/**
400421
* Registers a callback function as an `onRetain` Transition+State Hook.
@@ -432,7 +453,7 @@ export interface IHookRegistry {
432453
* @param callback the hook function which will be injected and invoked.
433454
* @returns a function which deregisters the hook.
434455
*/
435-
onRetain(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
456+
onRetain(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
436457

437458
/**
438459
* Registers a callback function as an `onExit` Transition+State Hook.
@@ -471,7 +492,7 @@ export interface IHookRegistry {
471492
* @param callback the hook function which will be injected and invoked.
472493
* @returns a function which deregisters the hook.
473494
*/
474-
onExit(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
495+
onExit(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
475496

476497
/**
477498
* Registers a callback function as an `onFinish` Transition Hook.
@@ -504,7 +525,7 @@ export interface IHookRegistry {
504525
* @param callback the hook function which will be injected and invoked.
505526
* @returns a function which deregisters the hook.
506527
*/
507-
onFinish(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
528+
onFinish(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
508529

509530
/**
510531
* Registers a callback function as an `onSuccess` Transition Hook.
@@ -531,7 +552,7 @@ export interface IHookRegistry {
531552
* @param callback the hook function which will be injected and invoked.
532553
* @returns a function which deregisters the hook.
533554
*/
534-
onSuccess(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
555+
onSuccess(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
535556

536557
/**
537558
* Registers a callback function as an `onError` Transition Hook.
@@ -557,7 +578,7 @@ export interface IHookRegistry {
557578
* @param callback the hook function which will be injected and invoked.
558579
* @returns a function which deregisters the hook.
559580
*/
560-
onError(matchCriteria: IMatchCriteria, callback: IInjectable, options?): Function;
581+
onError(matchCriteria: HookMatchCriteria, callback: IInjectable, options?: HookRegOptions): Function;
561582

562583
/**
563584
* Returns all the registered hooks of a given `hookName` type
@@ -571,13 +592,14 @@ export interface IHookRegistry {
571592
getHooks(hookName: string): IEventHook[];
572593
}
573594

595+
/** A predicate type which takes a [[State]] and returns a boolean */
574596
export type IStateMatch = Predicate<State>
575597
/**
576598
* This object is used to configure whether or not a Transition Hook is invoked for a particular transition,
577599
* based on the Transition's "to state" and "from state".
578600
*
579601
* Each property (`to`, `from`, `exiting`, `retained`, and `entering`) can be state globs, a function that takes a
580-
* state, or a boolean (see [[MatchCriterion]])
602+
* state, or a boolean (see [[HookMatchCriterion]])
581603
*
582604
* All properties are optional. If any property is omitted, it is replaced with the value `true`, and always matches.
583605
*
@@ -631,17 +653,17 @@ export type IStateMatch = Predicate<State>
631653
* }
632654
* ```
633655
*/
634-
export interface IMatchCriteria {
635-
/** A [[MatchCriterion]] to match the destination state */
636-
to?: MatchCriterion;
637-
/** A [[MatchCriterion]] to match the original (from) state */
638-
from?: MatchCriterion;
639-
/** A [[MatchCriterion]] to match any state that would be exiting */
640-
exiting?: MatchCriterion;
641-
/** A [[MatchCriterion]] to match any state that would be retained */
642-
retained?: MatchCriterion;
643-
/** A [[MatchCriterion]] to match any state that would be entering */
644-
entering?: MatchCriterion;
656+
export interface HookMatchCriteria {
657+
/** A [[HookMatchCriterion]] to match the destination state */
658+
to?: HookMatchCriterion;
659+
/** A [[HookMatchCriterion]] to match the original (from) state */
660+
from?: HookMatchCriterion;
661+
/** A [[HookMatchCriterion]] to match any state that would be exiting */
662+
exiting?: HookMatchCriterion;
663+
/** A [[HookMatchCriterion]] to match any state that would be retained */
664+
retained?: HookMatchCriterion;
665+
/** A [[HookMatchCriterion]] to match any state that would be entering */
666+
entering?: HookMatchCriterion;
645667
}
646668

647669
export interface IMatchingNodes {
@@ -658,10 +680,11 @@ export interface IMatchingNodes {
658680
* which should return a boolean to indicate if a state matches.
659681
* Or, `true` to match anything
660682
*/
661-
export type MatchCriterion = (string|IStateMatch|boolean)
683+
export type HookMatchCriterion = (string|IStateMatch|boolean)
662684

663685
export interface IEventHook {
664686
callback: IInjectable;
665687
priority: number;
688+
bind: any;
666689
matches: (treeChanges: TreeChanges) => IMatchingNodes;
667690
}

src/transition/transitionHook.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import {ResolveContext} from "../resolve/module";
1313

1414
let REJECT = new RejectFactory();
1515

16-
let defaultOptions = {
16+
let defaultOptions: TransitionHookOptions = {
1717
async: true,
1818
rejectIfSuperseded: true,
1919
current: noop,
2020
transition: null,
21-
traceData: {}
21+
traceData: {},
22+
bind: null
2223
};
2324

2425
export class TransitionHook {

test/hookBuilderSpec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe('HookBuilder:', function() {
6666
});
6767

6868

69-
describe('MatchCriteria', function() {
69+
describe('HookMatchCriteria', function() {
7070

7171
describe('.to', function() {
7272
it("should match a transition with same to state", function() {

test/transitionSpec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,7 @@ describe('transition', function () {
516516
});
517517
});
518518

519-
describe('Transition MatchCriterion', function() {
519+
xdescribe('Transition HookMatchCriterion', function() {
520520
it("should", function() {
521521

522522
})

0 commit comments

Comments
 (0)