diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 25311ae3a2..e7eddf4f4a 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -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, @@ -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; } diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 90ec332da1..a81eeb81c9 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -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 = { diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index 9f9bdc0b96..88070a4543 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -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); diff --git a/modules/@angular/compiler/src/ng_module_compiler.ts b/modules/@angular/compiler/src/ng_module_compiler.ts index 9ca32b71b6..4c0871818f 100644 --- a/modules/@angular/compiler/src/ng_module_compiler.ts +++ b/modules/@angular/compiler/src/ng_module_compiler.ts @@ -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); } } diff --git a/modules/@angular/compiler/src/private_import_core.ts b/modules/@angular/compiler/src/private_import_core.ts index cca7cca9df..5bd4cd4df1 100644 --- a/modules/@angular/compiler/src/private_import_core.ts +++ b/modules/@angular/compiler/src/private_import_core.ts @@ -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 = diff --git a/modules/@angular/core/src/core_private_export.ts b/modules/@angular/core/src/core_private_export.ts index bc491b6350..778b848446 100644 --- a/modules/@angular/core/src/core_private_export.ts +++ b/modules/@angular/core/src/core_private_export.ts @@ -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, NgModuleInjector: typeof ng_module_factory.NgModuleInjector, _NgModuleInjector?: ng_module_factory.NgModuleInjector, + 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, diff --git a/modules/@angular/core/src/linker.ts b/modules/@angular/core/src/linker.ts index 12d4c98334..b3b25b222e 100644 --- a/modules/@angular/core/src/linker.ts +++ b/modules/@angular/core/src/linker.ts @@ -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'; diff --git a/modules/@angular/core/src/linker/ng_module_factory_loader.ts b/modules/@angular/core/src/linker/ng_module_factory_loader.ts index 4f576725c7..951cce9bb8 100644 --- a/modules/@angular/core/src/linker/ng_module_factory_loader.ts +++ b/modules/@angular/core/src/linker/ng_module_factory_loader.ts @@ -15,3 +15,34 @@ import {NgModuleFactory} from './ng_module_factory'; export abstract class NgModuleFactoryLoader { abstract load(path: string): Promise>; } + +let moduleFactories = new Map>(); + +/** + * Registers a loaded module. Should only be called from generated NgModuleFactory code. + * @experimental + */ +export function registerModuleFactory(id: string, factory: NgModuleFactory) { + 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>(); +} + +/** + * 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 { + let factory = moduleFactories.get(id); + if (!factory) throw new Error(`No module with ID ${id} loaded`); + return factory; +} diff --git a/modules/@angular/core/src/metadata/ng_module.ts b/modules/@angular/core/src/metadata/ng_module.ts index 165e7ceb17..11c2c44442 100644 --- a/modules/@angular/core/src/metadata/ng_module.ts +++ b/modules/@angular/core/src/metadata/ng_module.ts @@ -59,6 +59,7 @@ export interface NgModuleMetadataType { entryComponents?: Array|any[]>; bootstrap?: Array|any[]>; schemas?: Array; + id?: string; } /** @@ -177,6 +178,13 @@ export class NgModuleMetadata extends InjectableMetadata implements NgModuleMeta */ schemas: Array; + /** + * 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; } } diff --git a/modules/@angular/core/test/linker/ng_module_integration_spec.ts b/modules/@angular/core/test/linker/ng_module_integration_spec.ts index 6c1fc80a0a..d193e22e53 100644 --- a/modules/@angular/core/test/linker/ng_module_integration_spec.ts +++ b/modules/@angular/core/test/linker/ng_module_integration_spec.ts @@ -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]}) diff --git a/tools/public_api_guard/core/index.d.ts b/tools/public_api_guard/core/index.d.ts index c4198427fb..e1b26fc9c5 100644 --- a/tools/public_api_guard/core/index.d.ts +++ b/tools/public_api_guard/core/index.d.ts @@ -539,6 +539,9 @@ export interface ForwardRefFn { /** @experimental */ export declare function getDebugNode(nativeNode: any): DebugNode; +/** @experimental */ +export declare function getModuleFactory(id: string): NgModuleFactory; + /** @experimental */ export declare function getPlatform(): PlatformRef; @@ -749,6 +752,7 @@ export declare class NgModuleMetadata extends InjectableMetadata implements NgMo declarations: Array | any[]>; entryComponents: Array | any[]>; exports: Array | any[]>; + id: string; imports: Array | ModuleWithProviders | any[]>; providers: Provider[]; schemas: Array; @@ -767,6 +771,7 @@ export interface NgModuleMetadataType { declarations?: Array | any[]>; entryComponents?: Array | any[]>; exports?: Array | any[]>; + id?: string; imports?: Array | ModuleWithProviders | any[]>; providers?: Provider[]; schemas?: Array;