|
1 | 1 | /** @module ng2 */ /** */
|
2 | 2 | import {NgModuleFactoryLoader, NgModuleRef, Injector, NgModuleFactory, Type, Compiler} from "@angular/core";
|
3 |
| - |
4 |
| -import {LazyLoadResult} from "ui-router-core"; |
5 |
| - |
6 |
| -import {Transition} from "ui-router-core"; |
| 3 | +import {Transition, LazyLoadResult, UIRouter, Resolvable, NATIVE_INJECTOR_TOKEN, isString} from "ui-router-core"; |
7 | 4 | import {RootModule, StatesModule, UIROUTER_ROOT_MODULE, UIROUTER_MODULE_TOKEN} from "./uiRouterNgModule";
|
8 | 5 | import {applyModuleConfig} from "./uiRouterConfig";
|
9 |
| -import {UIRouter} from "ui-router-core"; |
10 |
| -import {Resolvable} from "ui-router-core"; |
11 |
| -import {NATIVE_INJECTOR_TOKEN} from "ui-router-core"; |
12 | 6 |
|
13 | 7 | export type ModuleTypeCallback = () => Type<any> | Promise<Type<any>>;
|
| 8 | +export type NgModuleToLoad = string | ModuleTypeCallback; |
14 | 9 |
|
15 | 10 | /**
|
16 | 11 | * Returns a function which lazy loads a nested module
|
17 | 12 | *
|
18 |
| - * Use this function as a [[StateDeclaration.lazyLoad]] property to lazy load a state tree (an NgModule). |
| 13 | + * Use this function as a [[StateDeclaration.lazyLoad]] property to lazy load an NgModule and its state. |
19 | 14 | *
|
20 |
| - * @param path the path to the module source code. |
21 |
| - * @returns A function which takes a transition, then: |
| 15 | + * @param moduleToLoad |
| 16 | + * If a string, it should be the path to the NgModule code, which will then be loaded by the `NgModuleFactoryLoader`. |
| 17 | + * If a function, the function should load the NgModule code and return a reference to the `NgModule` class being loaded. |
22 | 18 | *
|
| 19 | + * @returns A function which takes a transition, which: |
23 | 20 | * - Gets the Injector (scoped properly for the destination state)
|
24 | 21 | * - Loads and creates the NgModule
|
25 | 22 | * - Finds the "replacement state" for the target state, and adds the new NgModule Injector to it (as a resolve)
|
| 23 | + * - Returns the new states array |
| 24 | + */ |
| 25 | +export function loadNgModule(moduleToLoad: NgModuleToLoad): (transition: Transition) => Promise<LazyLoadResult> { |
| 26 | + return function(transition: Transition) { |
| 27 | + const ng2Injector = transition.injector().get(NATIVE_INJECTOR_TOKEN); |
| 28 | + |
| 29 | + const createModule = (factory: NgModuleFactory<any>) => |
| 30 | + factory.create(ng2Injector); |
| 31 | + |
| 32 | + const applyModule = (moduleRef: NgModuleRef<any>) => |
| 33 | + applyNgModule(transition, moduleRef); |
| 34 | + |
| 35 | + return loadModuleFactory(moduleToLoad, ng2Injector) |
| 36 | + .then(createModule) |
| 37 | + .then(applyModule); |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Returns the module factory that can be used to instantiate a module |
| 43 | + * |
| 44 | + * For strings this: |
| 45 | + * - Finds the correct NgModuleFactoryLoader |
| 46 | + * - Loads the new NgModuleFactory from the path string (async) |
| 47 | + * |
| 48 | + * For a Type<any> or Promise<Type<any>> this: |
| 49 | + * - Compiles the component type (if not running with AOT) |
| 50 | + * - Returns the NgModuleFactory resulting from compilation (or direct loading if using AOT) as a Promise |
26 | 51 | *
|
27 |
| - * returns the new states array |
28 | 52 | */
|
29 |
| -export function loadNgModule( moduleToLoad: string | ModuleTypeCallback): (transition: Transition) => Promise<LazyLoadResult> { |
30 |
| - /** Get the parent NgModule Injector (from resolves) */ |
31 |
| - const getNg2Injector = (transition: Transition) => |
32 |
| - transition.injector().getAsync(NATIVE_INJECTOR_TOKEN); |
33 |
| - |
34 |
| - /** |
35 |
| - * Returns the module factory that can be used to instantiate a module |
36 |
| - * |
37 |
| - * For strings this: |
38 |
| - * - Finds the correct NgModuleFactoryLoader |
39 |
| - * - Loads the new NgModuleFactory from the path string (async) |
40 |
| - * |
41 |
| - * For a Type<any> or Promise<Type<any>> this: |
42 |
| - * - Compiles the component type (if not running with AOT) |
43 |
| - * - Returns the NgModuleFactory resulting from compilation (or direct loading if using AOT) as a Promise |
44 |
| - * |
45 |
| - */ |
46 |
| - const loadModuleFactory = (loadChildren: string | ModuleTypeCallback, ng2Injector: Injector): Promise<NgModuleFactory<any>>=>{ |
47 |
| - if(typeof(loadChildren) === 'string'){ |
48 |
| - return ng2Injector.get(NgModuleFactoryLoader).load(loadChildren); |
49 |
| - } |
50 |
| - else{ |
51 |
| - const compiler: Compiler = ng2Injector.get(Compiler); |
52 |
| - const offlineMode = compiler instanceof Compiler; |
53 |
| - const loadChildrenPromise = Promise.resolve(loadChildren()); |
54 |
| - return offlineMode ? loadChildrenPromise : loadChildrenPromise.then( moduleType => compiler.compileModuleAsync(moduleType)) |
55 |
| - } |
| 53 | +export function loadModuleFactory(moduleToLoad: NgModuleToLoad, ng2Injector: Injector): Promise<NgModuleFactory<any>> { |
| 54 | + if (isString(moduleToLoad)) { |
| 55 | + return ng2Injector.get(NgModuleFactoryLoader).load(moduleToLoad); |
56 | 56 | }
|
57 | 57 |
|
58 |
| - /** |
59 |
| - * Lazy loads the NgModule using the NgModuleFactoryLoader |
60 |
| - * |
61 |
| - * Use the parent NgModule's Injector to: |
62 |
| - * - Find the correct NgModuleFactory |
63 |
| - * - Create the new NgModule |
64 |
| - */ |
65 |
| - const createNg2Module = ( moduleToLoad: string | ModuleTypeCallback , ng2Injector: Injector) => |
66 |
| - loadModuleFactory(moduleToLoad, ng2Injector).then((factory: NgModuleFactory<any>) => |
67 |
| - factory.create(ng2Injector)); |
68 |
| - |
69 |
| - |
70 |
| - /** |
71 |
| - * Apply the UI-Router Modules found in the lazy loaded module. |
72 |
| - * |
73 |
| - * Apply the Lazy Loaded NgModule's newly created Injector to the right state in the state tree. |
74 |
| - * |
75 |
| - * Lazy loading uses a placeholder state which is removed (and replaced) after the module is loaded. |
76 |
| - * The NgModule should include a state with the same name as the placeholder. |
77 |
| - * |
78 |
| - * Find the *newly loaded state* with the same name as the *placeholder state*. |
79 |
| - * The NgModule's Injector (and ComponentFactoryResolver) will be added to that state. |
80 |
| - * The Injector/Factory are used when creating Components for the `replacement` state and all its children. |
81 |
| - */ |
82 |
| - function loadUIRouterModules(transition: Transition, ng2Module: NgModuleRef<any>): LazyLoadResult { |
83 |
| - let injector = ng2Module.injector; |
84 |
| - let parentInjector = <Injector> ng2Module.injector['parent']; |
85 |
| - let uiRouter: UIRouter = injector.get(UIRouter); |
86 |
| - |
87 |
| - let originalName = transition.to().name; |
88 |
| - let originalState = uiRouter.stateRegistry.get(originalName); |
89 |
| - |
90 |
| - let rootModules: RootModule[] = injector.get(UIROUTER_ROOT_MODULE); |
91 |
| - let parentRootModules: RootModule[] = parentInjector.get(UIROUTER_ROOT_MODULE); |
92 |
| - let newRootModules = rootModules.filter(module => parentRootModules.indexOf(module) === -1); |
93 |
| - |
94 |
| - if (newRootModules.length) { |
95 |
| - console.log(rootModules); |
96 |
| - throw new Error('Lazy loaded modules should not contain a UIRouterModule.forRoot() module'); |
97 |
| - } |
98 |
| - |
99 |
| - let modules: StatesModule[] = injector.get(UIROUTER_MODULE_TOKEN); |
100 |
| - modules.forEach(module => applyModuleConfig(uiRouter, injector, module)); |
101 |
| - |
102 |
| - let replacementState = uiRouter.stateRegistry.get(originalName); |
103 |
| - if (replacementState === originalState) { |
104 |
| - throw new Error(`The module that was loaded from ${moduleToLoad} should have a ui-router state named '${originalName}'`); |
105 |
| - } |
106 |
| - |
107 |
| - // Supply the newly loaded states with the Injector from the lazy loaded NgModule |
108 |
| - replacementState.$$state().resolvables.push(Resolvable.fromData(NATIVE_INJECTOR_TOKEN, injector)); |
109 |
| - |
110 |
| - return {}; |
| 58 | + const compiler: Compiler = ng2Injector.get(Compiler); |
| 59 | + const offlineMode = compiler instanceof Compiler; |
| 60 | + const loadChildrenPromise = Promise.resolve(moduleToLoad()); |
| 61 | + const compileAsync = (moduleType: Type<any>) => |
| 62 | + compiler.compileModuleAsync(moduleType); |
| 63 | + |
| 64 | + return offlineMode ? loadChildrenPromise : loadChildrenPromise.then(compileAsync) |
| 65 | +} |
| 66 | + |
| 67 | +/** |
| 68 | + * Apply the UI-Router Modules found in the lazy loaded module. |
| 69 | + * |
| 70 | + * Apply the Lazy Loaded NgModule's newly created Injector to the right state in the state tree. |
| 71 | + * |
| 72 | + * Lazy loading uses a placeholder state which is removed (and replaced) after the module is loaded. |
| 73 | + * The NgModule should include a state with the same name as the placeholder. |
| 74 | + * |
| 75 | + * Find the *newly loaded state* with the same name as the *placeholder state*. |
| 76 | + * The NgModule's Injector (and ComponentFactoryResolver) will be added to that state. |
| 77 | + * The Injector/Factory are used when creating Components for the `replacement` state and all its children. |
| 78 | + */ |
| 79 | +export function applyNgModule(transition: Transition, ng2Module: NgModuleRef<any>): LazyLoadResult { |
| 80 | + let injector = ng2Module.injector; |
| 81 | + let parentInjector = <Injector> ng2Module.injector['parent']; |
| 82 | + let uiRouter: UIRouter = injector.get(UIRouter); |
| 83 | + |
| 84 | + let originalName = transition.to().name; |
| 85 | + let originalState = uiRouter.stateRegistry.get(originalName); |
| 86 | + |
| 87 | + let rootModules: RootModule[] = injector.get(UIROUTER_ROOT_MODULE); |
| 88 | + let parentRootModules: RootModule[] = parentInjector.get(UIROUTER_ROOT_MODULE); |
| 89 | + let newRootModules = rootModules.filter(module => parentRootModules.indexOf(module) === -1); |
| 90 | + |
| 91 | + if (newRootModules.length) { |
| 92 | + console.log(rootModules); |
| 93 | + throw new Error('Lazy loaded modules should not contain a UIRouterModule.forRoot() module'); |
| 94 | + } |
| 95 | + |
| 96 | + let modules: StatesModule[] = injector.get(UIROUTER_MODULE_TOKEN); |
| 97 | + modules.forEach(module => applyModuleConfig(uiRouter, injector, module)); |
| 98 | + |
| 99 | + let replacementState = uiRouter.stateRegistry.get(originalName); |
| 100 | + if (replacementState === originalState) { |
| 101 | + throw new Error(`The module that was lazy loaded by activating ${originalName} should also have a ui-router state named '${originalName}'`); |
111 | 102 | }
|
112 | 103 |
|
113 |
| - return (transition: Transition) => getNg2Injector(transition) |
114 |
| - .then((injector: Injector) => createNg2Module(moduleToLoad, injector)) |
115 |
| - .then((moduleRef: NgModuleRef<any>) => loadUIRouterModules(transition, moduleRef)) |
| 104 | + // Supply the newly loaded states with the Injector from the lazy loaded NgModule |
| 105 | + replacementState.$$state().resolvables.push(Resolvable.fromData(NATIVE_INJECTOR_TOKEN, injector)); |
| 106 | + |
| 107 | + return {}; |
116 | 108 | }
|
0 commit comments