-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(lazyLoad): Add state.lazyLoad hook to lazy load a tree of states
- Loading branch information
1 parent
4440811
commit bef5257
Showing
14 changed files
with
216 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import {Transition} from "../transition/transition"; | ||
import {TransitionService} from "../transition/transitionService"; | ||
import {TransitionHookFn} from "../transition/interface"; | ||
import {StateDeclaration} from "../state/interface"; | ||
import {State} from "../state/stateObject"; | ||
import {services} from "../common/coreservices"; | ||
|
||
/** | ||
* A [[TransitionHookFn]] that lazy loads a state tree. | ||
* | ||
* When transitioning to a state "abc" which has a `lazyLoad` function defined: | ||
* - Invoke the `lazyLoad` function | ||
* - The function should return a promise for an array of lazy loaded [[StateDeclaration]]s | ||
* - Wait for the promise to resolve | ||
* - Deregister the original state "abc" | ||
* - The original state definition is a placeholder for the lazy loaded states | ||
* - Register the new states | ||
* - Retry the transition | ||
* | ||
* See [[StateDeclaration.lazyLoad]] | ||
*/ | ||
const lazyLoadHook: TransitionHookFn = (transition: Transition) => { | ||
var toState = transition.to(); | ||
|
||
function retryOriginalTransition(newStates: State[]) { | ||
if (transition.options().source === 'url') { | ||
let loc = services.location; | ||
let path = loc.path(), search = loc.search(), hash = loc.hash(); | ||
|
||
let matchState = state => [state, state.url.exec(path, search, hash)]; | ||
let matches = newStates.map(matchState).filter(([state, params]) => !!params); | ||
if (matches.length) { | ||
let [state, params] = matches[0]; | ||
return transition.router.stateService.target(state, params, transition.options()); | ||
} | ||
transition.router.urlRouter.sync(); | ||
} | ||
|
||
let state = transition.targetState().identifier(); | ||
let params = transition.params(); | ||
let options = transition.options(); | ||
return transition.router.stateService.target(state, params, options); | ||
} | ||
|
||
/** | ||
* Replace the placeholder state with the newly loaded states from the NgModule. | ||
*/ | ||
function updateStateRegistry(newStates: StateDeclaration[]) { | ||
let registry = transition.router.stateRegistry; | ||
let placeholderState = transition.to(); | ||
|
||
registry.deregister(placeholderState); | ||
newStates.forEach(state => registry.register(state)); | ||
return newStates.map(state => registry.get(state).$$state()); | ||
} | ||
|
||
return toState.lazyLoad(transition) | ||
.then(updateStateRegistry) | ||
.then(retryOriginalTransition) | ||
}; | ||
|
||
export const registerLazyLoadHook = (transitionService: TransitionService) => | ||
transitionService.onBefore({ to: (state) => !!state.lazyLoad }, lazyLoadHook); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import {Transition} from "../transition/transition"; | ||
import {NG2_INJECTOR_TOKEN, Ng2StateDeclaration} from "./interface"; | ||
import {UIROUTER_STATES_TOKEN} from "./uiRouterNgModule"; | ||
|
||
import {NgModuleFactoryLoader, NgModuleRef, Injector, NgModuleFactory} from "@angular/core"; | ||
import {unnestR} from "../common/common"; | ||
|
||
/** | ||
* Returns a function which lazy loads a nested module | ||
* | ||
* Use this function as a [[StateDeclaration.lazyLoad]] property to lazy load a state tree (an NgModule). | ||
* | ||
* @param path the path to the module source code. | ||
* @returns A function which takes a transition, then: | ||
* | ||
* - Gets the Injector (scoped properly for the destination state) | ||
* - Loads and creates the NgModule | ||
* - Finds the "replacement state" for the target state, and adds the new NgModule Injector to it (as a resolve) | ||
* | ||
* returns the new states array | ||
*/ | ||
export function loadNgModule(path: string) { | ||
/** Get the parent NgModule Injector (from resolves) */ | ||
const getNg2Injector = (transition: Transition) => | ||
transition.injector().getAsync(NG2_INJECTOR_TOKEN); | ||
|
||
/** | ||
* Lazy loads the NgModule using the NgModuleFactoryLoader | ||
* | ||
* Use the parent NgModule's Injector to: | ||
* - Find the correct NgModuleFactoryLoader | ||
* - Load the new NgModuleFactory from the path string (async) | ||
* - Create the new NgModule | ||
*/ | ||
const createNg2Module = (path: string, ng2Injector: Injector) => | ||
ng2Injector.get(NgModuleFactoryLoader).load(path) | ||
.then((factory: NgModuleFactory<any>) => factory.create(ng2Injector)); | ||
|
||
/** | ||
* Apply the Lazy Loaded NgModule's Injector to the newly loaded state tree. | ||
* | ||
* Lazy loading uses a placeholder state which is removed (and replaced) after the module is loaded. | ||
* The NgModule should include a state with the same name as the placeholder. | ||
* | ||
* Find the *newly loaded state* with the same name as the *placeholder state*. | ||
* The NgModule's Injector (and ComponentFactoryResolver) will be added to that state. | ||
* The Injector/Factory are used when creating Components for the `replacement` state and all its children. | ||
*/ | ||
function applyNgModuleToNewStates(transition: Transition, ng2Module: NgModuleRef<any>): Ng2StateDeclaration[] { | ||
var targetName = transition.to().name; | ||
let newStates: Ng2StateDeclaration[] = ng2Module.injector.get(UIROUTER_STATES_TOKEN).reduce(unnestR, []); | ||
let replacementState = newStates.find(state => state.name === targetName); | ||
|
||
if (!replacementState) { | ||
throw new Error(`The module that was loaded from ${path} should have a state named '${targetName}'` + | ||
`, but it only had: ${(newStates || []).map(s=>s.name).join(', ')}`); | ||
} | ||
|
||
// Add the injector as a resolve. | ||
replacementState['_ngModuleInjector'] = ng2Module.injector; | ||
|
||
return newStates; | ||
} | ||
|
||
return (transition: Transition) => getNg2Injector(transition) | ||
.then((injector: Injector) => createNg2Module(path, injector)) | ||
.then((moduleRef: NgModuleRef<any>) => applyNgModuleToNewStates(transition, moduleRef)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** @module ng2 */ /** */ | ||
import {State} from "../../state/stateObject"; | ||
import {NG2_INJECTOR_TOKEN} from "../interface"; | ||
import {Resolvable} from "../../resolve/resolvable"; | ||
|
||
/** | ||
* This is a [[StateBuilder.builder]] function which enables lazy Ng2Module support. | ||
* | ||
* See [[loadNgModule]] | ||
* | ||
* After lazy loading an NgModule, any Components from that module should be created using the NgModule's Injecjtor. | ||
* The NgModule's ComponentFactory only exists inside that Injector. | ||
* | ||
* After lazy loading an NgModule, it is stored on the root state of the lazy loaded state tree. | ||
* When instantiating Component, the parent Component's Injector is merged with the NgModule injector. | ||
*/ | ||
export function ngModuleResolvablesBuilder(state: State, parentFn: Function): Resolvable[] { | ||
let resolvables: Resolvable[] = parentFn(state); | ||
let injector = state.self['_ngModuleInjector']; | ||
return !injector ? resolvables : resolvables.concat(Resolvable.fromData(NG2_INJECTOR_TOKEN, injector)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters