diff --git a/src/hooks/resolve.ts b/src/hooks/resolve.ts index 25d70cb0..d2557370 100644 --- a/src/hooks/resolve.ts +++ b/src/hooks/resolve.ts @@ -8,10 +8,12 @@ import { TransitionService } from '../transition/transitionService'; import { val } from '../common/hof'; import { StateDeclaration } from '../state/interface'; +export const RESOLVE_HOOK_PRIORITY = 1000; + /** * A [[TransitionHookFn]] which resolves all EAGER Resolvables in the To Path * - * Registered using `transitionService.onStart({}, eagerResolvePath);` + * Registered using `transitionService.onStart({}, eagerResolvePath, { priority: 1000 });` * * When a Transition starts, this hook resolves all the EAGER Resolvables, which the transition then waits for. * @@ -23,12 +25,12 @@ const eagerResolvePath: TransitionHookFn = (trans: Transition) => .then(noop); export const registerEagerResolvePath = (transitionService: TransitionService) => - transitionService.onStart({}, eagerResolvePath, {priority: 1000}); + transitionService.onStart({}, eagerResolvePath, {priority: RESOLVE_HOOK_PRIORITY}); /** * A [[TransitionHookFn]] which resolves all LAZY Resolvables for the state (and all its ancestors) in the To Path * - * Registered using `transitionService.onEnter({ entering: () => true }, lazyResolveState);` + * Registered using `transitionService.onEnter({ entering: () => true }, lazyResolveState, { priority: 1000 });` * * When a State is being entered, this hook resolves all the Resolvables for this state, which the transition then waits for. * @@ -41,5 +43,23 @@ const lazyResolveState: TransitionStateHookFn = (trans: Transition, state: State .then(noop); export const registerLazyResolveState = (transitionService: TransitionService) => - transitionService.onEnter({ entering: val(true) }, lazyResolveState, {priority: 1000}); + transitionService.onEnter({ entering: val(true) }, lazyResolveState, {priority: RESOLVE_HOOK_PRIORITY}); + + +/** + * A [[TransitionHookFn]] which resolves any dynamically added (LAZY or EAGER) Resolvables. + * + * Registered using `transitionService.onFinish({}, eagerResolvePath, { priority: 1000 });` + * + * After all entering states have been entered, this hook resolves any remaining Resolvables. + * These are typically dynamic resolves which were added by some Transition Hook using [[Transition.addResolvable]]. + * + * See [[StateDeclaration.resolve]] + */ +const resolveRemaining: TransitionHookFn = (trans: Transition) => + new ResolveContext(trans.treeChanges().to) + .resolvePath("LAZY", trans) + .then(noop); +export const registerResolveRemaining = (transitionService: TransitionService) => + transitionService.onFinish({}, resolveRemaining, {priority: RESOLVE_HOOK_PRIORITY}); diff --git a/src/transition/transitionService.ts b/src/transition/transitionService.ts index b91d3003..adf5b466 100644 --- a/src/transition/transitionService.ts +++ b/src/transition/transitionService.ts @@ -16,7 +16,7 @@ import { UIRouter } from "../router"; import { registerAddCoreResolvables } from "../hooks/coreResolvables"; import { registerRedirectToHook } from "../hooks/redirectTo"; import { registerOnExitHook, registerOnRetainHook, registerOnEnterHook } from "../hooks/onEnterExitRetain"; -import { registerEagerResolvePath, registerLazyResolveState } from "../hooks/resolve"; +import { registerEagerResolvePath, registerLazyResolveState, registerResolveRemaining } from "../hooks/resolve"; import { registerLoadEnteringViews, registerActivateViews } from "../hooks/views"; import { registerUpdateGlobalState } from "../hooks/updateGlobals"; import { registerUpdateUrl } from "../hooks/url"; @@ -180,6 +180,7 @@ export class TransitionService implements IHookRegistry, Disposable { onEnter: Function; eagerResolve: Function; lazyResolve: Function; + resolveAll: Function; loadViews: Function; activateViews: Function; updateGlobals: Function; @@ -328,7 +329,7 @@ export class TransitionService implements IHookRegistry, Disposable { // Wire up redirectTo hook fns.redirectTo = registerRedirectToHook(this); - + // Wire up onExit/Retain/Enter state hooks fns.onExit = registerOnExitHook(this); fns.onRetain = registerOnRetainHook(this); @@ -337,7 +338,8 @@ export class TransitionService implements IHookRegistry, Disposable { // 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); fns.activateViews = registerActivateViews(this); diff --git a/test/transitionSpec.ts b/test/transitionSpec.ts index efcdf40e..f86b889c 100644 --- a/test/transitionSpec.ts +++ b/test/transitionSpec.ts @@ -657,7 +657,7 @@ describe('transition', function () { .then(done, done); })); - it("hooks can add resolves to a $transition$ and they will be available to be injected elsewhere", ((done) => { + it("hooks can add resolves to a $transition$ and they will be available to be injected in nested states", ((done) => { let log = [], transition = makeTransition("A", "D"); let $q = services.$q; let defer = $q.defer(); @@ -687,6 +687,38 @@ describe('transition', function () { .then(done, done); })); + // test for https://github.com/angular-ui/ui-router/issues/3544 + it("hooks can add resolves to a $transition$ and they will be available in onSuccess", ((done) => { + let log = [], transition = makeTransition("A", "B"); + let $q = services.$q; + let defer = $q.defer(); + + $transitions.onEnter({ entering: '**'}, function logEnter(trans, state) { + log.push("Entered#" + state.name); + }, { priority: -1 }); + + $transitions.onEnter({ entering: "B" }, function addResolves($transition$: Transition) { + log.push("adding resolve"); + let resolveFn = function () { log.push("resolving"); return defer.promise; }; + $transition$.addResolvable(new Resolvable('newResolve', resolveFn)); + }); + + $transitions.onSuccess({}, function useTheNewResolve(trans) { + log.push('SUCCESS!'); + log.push(trans.injector().get('newResolve')); + }); + + transition.promise.then(function() { log.push("DONE!"); }); + + transition.run(); + + tick().then(() => expect(log.join(';')).toBe("adding resolve;Entered#B;resolving")) + .then(() => defer.resolve("resolvedval")) + .then(tick, tick) + .then(() => expect(log.join(';')).toBe("adding resolve;Entered#B;resolving;SUCCESS!;resolvedval;DONE!")) + .then(done, done); + })); + // Test for https://github.com/ui-router/core/issues/32 it('should match "" (empty string) to root state only', async (done) => { const beforeLog = [], successLog = [];