feat(router): register NgModuleFactory objects. (#11211)

When lazily loading code, users need to be able to get hold of the
NgModuleFactory. For SystemJS environments, the SystemJS registry serves
this purpose. However other environments, such as modules compiled with
Closure compiler, do not expose exports object or a path based registry.

For these environments, `@NgModule` objects can include an identifier, and
the loading code can then pass `loadModule(id).then(() =>
getNgModule(id))` to the router.
This commit is contained in:
Martin Probst 2016-09-01 13:46:08 -07:00 committed by GitHub
parent c9e5b599e4
commit ebc8e808a9
11 changed files with 99 additions and 8 deletions

View File

@ -525,13 +525,14 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
importedModules: CompileNgModuleMetadata[];
exportedModules: CompileNgModuleMetadata[];
schemas: SchemaMetadata[];
id: string;
transitiveModule: TransitiveCompileNgModuleMetadata;
constructor(
{type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes,
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
transitiveModule}: {
transitiveModule, id}: {
type?: CompileTypeMetadata,
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
@ -544,7 +545,8 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata,
schemas?: SchemaMetadata[]
schemas?: SchemaMetadata[],
id?: string
} = {}) {
this.type = type;
this.declaredDirectives = _normalizeArray(declaredDirectives);
@ -557,6 +559,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
this.importedModules = _normalizeArray(importedModules);
this.exportedModules = _normalizeArray(exportedModules);
this.schemas = _normalizeArray(schemas);
this.id = id;
this.transitiveModule = transitiveModule;
}

View File

@ -9,7 +9,7 @@
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ElementRef, Injector, LOCALE_ID as LOCALE_ID_, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT as TRANSLATIONS_FORMAT_, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationOutput, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, renderStyles} from './private_import_core';
import {AnimationGroupPlayer, AnimationKeyframe, AnimationOutput, AnimationSequencePlayer, AnimationStyles, AppElement, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes, castByValue, checkBinding, clearStyles, collectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, reflector, registerModuleFactory, renderStyles} from './private_import_core';
import {assetUrl} from './util';
var APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
@ -107,6 +107,11 @@ export class Identifiers {
runtime: NgModuleInjector,
moduleUrl: assetUrl('core', 'linker/ng_module_factory')
};
static RegisterModuleFactoryFn: IdentifierSpec = {
name: 'registerModuleFactory',
runtime: registerModuleFactory,
moduleUrl: assetUrl('core', 'linker/ng_module_factory_loader')
};
static ValueUnwrapper:
IdentifierSpec = {name: 'ValueUnwrapper', moduleUrl: CD_MODULE_URL, runtime: ValueUnwrapper};
static Injector: IdentifierSpec = {

View File

@ -328,7 +328,8 @@ export class CompileMetadataResolver {
exportedPipes: exportedPipes,
importedModules: importedModules,
exportedModules: exportedModules,
transitiveModule: transitiveModule
transitiveModule: transitiveModule,
id: meta.id,
});
transitiveModule.modules.push(compileMeta);
this._verifyModule(compileMeta);

View File

@ -69,8 +69,16 @@ export class NgModuleCompiler {
[o.importType(ngModuleMeta.type)], [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]);
return new NgModuleCompileResult(
[injectorClass, ngModuleFactoryStmt], ngModuleFactoryVar, deps);
let stmts: o.Statement[] = [injectorClass, ngModuleFactoryStmt];
if (ngModuleMeta.id) {
let registerFactoryStmt =
o.importExpr(resolveIdentifier(Identifiers.RegisterModuleFactoryFn))
.callFn([o.literal(ngModuleMeta.id), o.variable(ngModuleFactoryVar)])
.toStmt();
stmts.push(registerFactoryStmt);
}
return new NgModuleCompileResult(stmts, ngModuleFactoryVar, deps);
}
}

View File

@ -25,6 +25,7 @@ export const CodegenComponentFactoryResolver: typeof r.CodegenComponentFactoryRe
export const AppView: typeof r.AppView = r.AppView;
export const DebugAppView: typeof r.DebugAppView = r.DebugAppView;
export const NgModuleInjector: typeof r.NgModuleInjector = r.NgModuleInjector;
export const registerModuleFactory: typeof r.registerModuleFactory = r.registerModuleFactory;
export type ViewType = typeof r._ViewType;
export const ViewType: typeof r.ViewType = r.ViewType;
export const MAX_INTERPOLATION_VALUES: typeof r.MAX_INTERPOLATION_VALUES =

View File

@ -24,6 +24,7 @@ import * as component_factory_resolver from './linker/component_factory_resolver
import * as debug_context from './linker/debug_context';
import * as element from './linker/element';
import * as ng_module_factory from './linker/ng_module_factory';
import * as ng_module_factory_loader from './linker/ng_module_factory_loader';
import * as template_ref from './linker/template_ref';
import * as view from './linker/view';
import * as view_type from './linker/view_type';
@ -61,6 +62,7 @@ export var __core_private__: {
DebugAppView: typeof view.DebugAppView, _DebugAppView?: view.DebugAppView<any>,
NgModuleInjector: typeof ng_module_factory.NgModuleInjector,
_NgModuleInjector?: ng_module_factory.NgModuleInjector<any>,
registerModuleFactory: typeof ng_module_factory_loader.registerModuleFactory,
ViewType: typeof view_type.ViewType, _ViewType?: view_type.ViewType,
MAX_INTERPOLATION_VALUES: typeof view_utils.MAX_INTERPOLATION_VALUES,
checkBinding: typeof view_utils.checkBinding,
@ -131,6 +133,7 @@ export var __core_private__: {
AppView: view.AppView,
DebugAppView: view.DebugAppView,
NgModuleInjector: ng_module_factory.NgModuleInjector,
registerModuleFactory: ng_module_factory_loader.registerModuleFactory,
ViewType: view_type.ViewType,
MAX_INTERPOLATION_VALUES: view_utils.MAX_INTERPOLATION_VALUES,
checkBinding: view_utils.checkBinding,

View File

@ -12,7 +12,7 @@ export {ComponentFactory, ComponentRef} from './linker/component_factory';
export {ComponentFactoryResolver} from './linker/component_factory_resolver';
export {ElementRef} from './linker/element_ref';
export {NgModuleFactory, NgModuleRef} from './linker/ng_module_factory';
export {NgModuleFactoryLoader} from './linker/ng_module_factory_loader';
export {NgModuleFactoryLoader, getModuleFactory} from './linker/ng_module_factory_loader';
export {QueryList} from './linker/query_list';
export {SystemJsNgModuleLoader, SystemJsNgModuleLoaderConfig} from './linker/system_js_ng_module_factory_loader';
export {TemplateRef} from './linker/template_ref';

View File

@ -15,3 +15,34 @@ import {NgModuleFactory} from './ng_module_factory';
export abstract class NgModuleFactoryLoader {
abstract load(path: string): Promise<NgModuleFactory<any>>;
}
let moduleFactories = new Map<string, NgModuleFactory<any>>();
/**
* Registers a loaded module. Should only be called from generated NgModuleFactory code.
* @experimental
*/
export function registerModuleFactory(id: string, factory: NgModuleFactory<any>) {
let existing = moduleFactories.get(id);
if (existing) {
throw new Error(`Duplicate module registered for ${id
} - ${existing.moduleType.name} vs ${factory.moduleType.name}`);
}
moduleFactories.set(id, factory);
}
export function clearModulesForTest() {
moduleFactories = new Map<string, NgModuleFactory<any>>();
}
/**
* Returns the NgModuleFactory with the given id, if it exists and has been loaded.
* Factories for modules that do not specify an `id` cannot be retrieved. Throws if the module
* cannot be found.
* @experimental
*/
export function getModuleFactory(id: string): NgModuleFactory<any> {
let factory = moduleFactories.get(id);
if (!factory) throw new Error(`No module with ID ${id} loaded`);
return factory;
}

View File

@ -59,6 +59,7 @@ export interface NgModuleMetadataType {
entryComponents?: Array<Type<any>|any[]>;
bootstrap?: Array<Type<any>|any[]>;
schemas?: Array<SchemaMetadata|any[]>;
id?: string;
}
/**
@ -177,6 +178,13 @@ export class NgModuleMetadata extends InjectableMetadata implements NgModuleMeta
*/
schemas: Array<SchemaMetadata|any[]>;
/**
* An opaque ID for this module, e.g. a name or a path. Used to identify modules in
* `getModuleFactory`. If left `undefined`, the `NgModule` will not be registered with
* `getModuleFactory`.
*/
id: string;
constructor(options: NgModuleMetadataType = {}) {
// We cannot use destructuring of the constructor argument because `exports` is a
// protected symbol in CommonJS and closure tries to aggressively optimize it away.
@ -188,5 +196,6 @@ export class NgModuleMetadata extends InjectableMetadata implements NgModuleMeta
this.entryComponents = options.entryComponents;
this.bootstrap = options.bootstrap;
this.schemas = options.schemas;
this.id = options.id;
}
}

View File

@ -6,13 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, SelfMetadata, Type, forwardRef} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, ComponentFactoryResolver, Directive, HostBinding, Inject, Injectable, Injector, Input, NgModule, NgModuleRef, Optional, Pipe, Provider, SelfMetadata, Type, forwardRef, getModuleFactory} from '@angular/core';
import {Console} from '@angular/core/src/console';
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers';
import {stringify} from '../../src/facade/lang';
import {NgModuleInjector} from '../../src/linker/ng_module_factory';
import {clearModulesForTest} from '../../src/linker/ng_module_factory_loader';
class Engine {}
@ -246,6 +247,30 @@ function declareTests({useJit}: {useJit: boolean}) {
});
});
describe('id', () => {
const token = 'myid';
@NgModule({id: token})
class SomeModule {
}
@NgModule({id: token})
class SomeOtherModule {
}
afterEach(() => clearModulesForTest());
it('should register loaded modules', () => {
createModule(SomeModule);
let factory = getModuleFactory(token);
expect(factory).toBeTruthy();
expect(factory.moduleType).toBe(SomeModule);
});
it('should throw when registering a duplicate module', () => {
createModule(SomeModule);
expect(() => createModule(SomeOtherModule)).toThrowError(/Duplicate module registered/);
});
});
describe('entryComponents', () => {
it('should create ComponentFactories in root modules', () => {
@NgModule({declarations: [SomeComp], entryComponents: [SomeComp]})

View File

@ -539,6 +539,9 @@ export interface ForwardRefFn {
/** @experimental */
export declare function getDebugNode(nativeNode: any): DebugNode;
/** @experimental */
export declare function getModuleFactory(id: string): NgModuleFactory<any>;
/** @experimental */
export declare function getPlatform(): PlatformRef;
@ -749,6 +752,7 @@ export declare class NgModuleMetadata extends InjectableMetadata implements NgMo
declarations: Array<Type<any> | any[]>;
entryComponents: Array<Type<any> | any[]>;
exports: Array<Type<any> | any[]>;
id: string;
imports: Array<Type<any> | ModuleWithProviders | any[]>;
providers: Provider[];
schemas: Array<SchemaMetadata | any[]>;
@ -767,6 +771,7 @@ export interface NgModuleMetadataType {
declarations?: Array<Type<any> | any[]>;
entryComponents?: Array<Type<any> | any[]>;
exports?: Array<Type<any> | any[]>;
id?: string;
imports?: Array<Type<any> | ModuleWithProviders | any[]>;
providers?: Provider[];
schemas?: Array<SchemaMetadata | any[]>;