From 6c5b653593eff19c5b9342b2cf0195aca49379cb Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Wed, 22 Jun 2016 14:06:23 -0700 Subject: [PATCH] feat(core): add `@Component.precompile` and `ComponentFactoryResolver` Part to #9467 Closes #9543 --- .../integrationtest/src/precompile.ts | 18 ++++ .../integrationtest/test/precompile_spec.ts | 27 +++++ modules/@angular/compiler/core_private.ts | 2 + .../@angular/compiler/src/compile_metadata.ts | 15 ++- .../compiler/src/directive_normalizer.ts | 1 + .../compiler/src/directive_resolver.ts | 3 +- modules/@angular/compiler/src/identifiers.ts | 14 ++- .../compiler/src/metadata_resolver.ts | 11 +- .../@angular/compiler/src/offline_compiler.ts | 24 +++-- .../@angular/compiler/src/runtime_compiler.ts | 102 ++++++++++-------- .../src/view_compiler/compile_element.ts | 65 +++++++---- .../src/view_compiler/view_builder.ts | 41 ++++--- .../src/view_compiler/view_compiler.ts | 10 +- .../testing/directive_resolver_mock.ts | 3 +- modules/@angular/core/private_export.ts | 4 + .../core/src/application_common_providers.ts | 2 + modules/@angular/core/src/linker.ts | 1 + .../src/linker/component_factory_resolver.ts | 49 +++++++++ modules/@angular/core/src/metadata.ts | 6 +- .../@angular/core/src/metadata/directives.ts | 15 ++- .../linker/precompile_integration_spec.ts | 100 +++++++++++++++++ modules/@angular/facade/src/lang.ts | 2 + tools/public_api_guard/public_api_spec.ts | 14 ++- 23 files changed, 419 insertions(+), 110 deletions(-) create mode 100644 modules/@angular/compiler-cli/integrationtest/src/precompile.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/test/precompile_spec.ts create mode 100644 modules/@angular/core/src/linker/component_factory_resolver.ts create mode 100644 modules/@angular/core/test/linker/precompile_integration_spec.ts diff --git a/modules/@angular/compiler-cli/integrationtest/src/precompile.ts b/modules/@angular/compiler-cli/integrationtest/src/precompile.ts new file mode 100644 index 0000000000..32172c0eea --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/src/precompile.ts @@ -0,0 +1,18 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component, ComponentFactoryResolver, Inject, OpaqueToken} from '@angular/core'; + +@Component({selector: 'cmp', template: ''}) +export class SomeComp { +} + +@Component({selector: 'cmp-precompile', template: '', precompile: [SomeComp]}) +export class CompWithPrecompile { + constructor(public cfr: ComponentFactoryResolver) {} +} diff --git a/modules/@angular/compiler-cli/integrationtest/test/precompile_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/precompile_spec.ts new file mode 100644 index 0000000000..722ec5c8db --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/test/precompile_spec.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {DebugElement, ReflectiveInjector, getDebugNode, lockRunMode} from '@angular/core'; +import {BROWSER_APP_PROVIDERS, By} from '@angular/platform-browser'; +import {serverPlatform} from '@angular/platform-server'; + +import {SomeComp} from '../src/precompile'; +import {CompWithPrecompileNgFactory} from '../src/precompile.ngfactory'; + +// Need to lock the mode explicitely as this test is not using Angular's testing framework. +lockRunMode(); + +describe('content projection', () => { + it('should support basic content projection', () => { + const appInjector = + ReflectiveInjector.resolveAndCreate(BROWSER_APP_PROVIDERS, serverPlatform().injector); + var compWithPrecompile = CompWithPrecompileNgFactory.create(appInjector).instance; + var cf = compWithPrecompile.cfr.resolveComponentFactory(SomeComp); + expect(cf.componentType).toBe(SomeComp); + }); +}); diff --git a/modules/@angular/compiler/core_private.ts b/modules/@angular/compiler/core_private.ts index afad93146f..c00da406ca 100644 --- a/modules/@angular/compiler/core_private.ts +++ b/modules/@angular/compiler/core_private.ts @@ -24,6 +24,8 @@ export var ReflectorComponentResolver: typeof t.ReflectorComponentResolver = r.ReflectorComponentResolver; export type AppElement = t.AppElement; export var AppElement: typeof t.AppElement = r.AppElement; +export var CodegenComponentFactoryResolver: typeof t.CodegenComponentFactoryResolver = + r.CodegenComponentFactoryResolver; export var AppView: typeof t.AppView = r.AppView; export type DebugAppView = t.DebugAppView; export var DebugAppView: typeof t.DebugAppView = r.DebugAppView; diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index 5391de2c1d..3e3cb5fb0f 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -675,7 +675,7 @@ export class CompileTemplateMetadata { export class CompileDirectiveMetadata implements CompileMetadataWithType { static create( {type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, - lifecycleHooks, providers, viewProviders, queries, viewQueries, template}: { + lifecycleHooks, providers, viewProviders, queries, viewQueries, precompile, template}: { type?: CompileTypeMetadata, isComponent?: boolean, selector?: string, @@ -691,6 +691,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { Array, queries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[], + precompile?: CompileTypeMetadata[], template?: CompileTemplateMetadata } = {}): CompileDirectiveMetadata { var hostListeners: {[key: string]: string} = {}; @@ -743,6 +744,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { viewProviders: viewProviders, queries: queries, viewQueries: viewQueries, + precompile: precompile, template: template }); } @@ -761,12 +763,13 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { viewProviders: CompileProviderMetadata[]; queries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[]; + precompile: CompileTypeMetadata[]; template: CompileTemplateMetadata; constructor( {type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners, hostProperties, hostAttributes, lifecycleHooks, providers, viewProviders, queries, - viewQueries, template}: { + viewQueries, precompile, template}: { type?: CompileTypeMetadata, isComponent?: boolean, selector?: string, @@ -784,6 +787,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { Array, queries?: CompileQueryMetadata[], viewQueries?: CompileQueryMetadata[], + precompile?: CompileTypeMetadata[], template?: CompileTemplateMetadata } = {}) { this.type = type; @@ -801,6 +805,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { this.viewProviders = _normalizeArray(viewProviders); this.queries = _normalizeArray(queries); this.viewQueries = _normalizeArray(viewQueries); + this.precompile = _normalizeArray(precompile); this.template = template; } @@ -827,7 +832,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { providers: _arrayFromJson(data['providers'], metadataFromJson), viewProviders: _arrayFromJson(data['viewProviders'], metadataFromJson), queries: _arrayFromJson(data['queries'], CompileQueryMetadata.fromJson), - viewQueries: _arrayFromJson(data['viewQueries'], CompileQueryMetadata.fromJson) + viewQueries: _arrayFromJson(data['viewQueries'], CompileQueryMetadata.fromJson), + precompile: _arrayFromJson(data['precompile'], CompileTypeMetadata.fromJson) }); } @@ -850,7 +856,8 @@ export class CompileDirectiveMetadata implements CompileMetadataWithType { 'providers': _arrayToJson(this.providers), 'viewProviders': _arrayToJson(this.viewProviders), 'queries': _arrayToJson(this.queries), - 'viewQueries': _arrayToJson(this.viewQueries) + 'viewQueries': _arrayToJson(this.viewQueries), + 'precompile': _arrayToJson(this.precompile) }; } } diff --git a/modules/@angular/compiler/src/directive_normalizer.ts b/modules/@angular/compiler/src/directive_normalizer.ts index 2dea7b91d6..83d812f47e 100644 --- a/modules/@angular/compiler/src/directive_normalizer.ts +++ b/modules/@angular/compiler/src/directive_normalizer.ts @@ -52,6 +52,7 @@ export class DirectiveNormalizer { viewProviders: directive.viewProviders, queries: directive.queries, viewQueries: directive.viewQueries, + precompile: directive.precompile, template: normalizedTemplate })); } diff --git a/modules/@angular/compiler/src/directive_resolver.ts b/modules/@angular/compiler/src/directive_resolver.ts index 6c60903193..fecbc5516f 100644 --- a/modules/@angular/compiler/src/directive_resolver.ts +++ b/modules/@angular/compiler/src/directive_resolver.ts @@ -125,7 +125,8 @@ export class DirectiveResolver { queries: mergedQueries, changeDetection: dm.changeDetection, providers: dm.providers, - viewProviders: dm.viewProviders + viewProviders: dm.viewProviders, + precompile: dm.precompile }); } else { diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index 50d25a7eb7..7da407b174 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectionStrategy, ChangeDetectorRef, ElementRef, Injector, QueryList, RenderComponentType, Renderer, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactoryResolver, ElementRef, Injector, QueryList, RenderComponentType, Renderer, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; -import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppView, ChangeDetectorState, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NoOpAnimationPlayer as NoOpAnimationPlayer_, SecurityContext, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles, uninitialized} from '../core_private'; +import {AnimationGroupPlayer as AnimationGroupPlayer_, AnimationKeyframe as AnimationKeyframe_, AnimationSequencePlayer as AnimationSequencePlayer_, AnimationStyles as AnimationStyles_, AppElement, AppView, ChangeDetectorState, CodegenComponentFactoryResolver, DebugAppView, DebugContext, EMPTY_ARRAY, EMPTY_MAP, NoOpAnimationPlayer as NoOpAnimationPlayer_, SecurityContext, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewType, ViewUtils, balanceAnimationKeyframes as impBalanceAnimationKeyframes, castByValue, checkBinding, clearStyles as impClearStyles, collectAndResolveStyles as impCollectAndResolveStyles, devModeEqual, flattenNestedViewRenderNodes, interpolate, prepareFinalAnimationStyles as impBalanceAnimationStyles, pureProxy1, pureProxy10, pureProxy2, pureProxy3, pureProxy4, pureProxy5, pureProxy6, pureProxy7, pureProxy8, pureProxy9, renderStyles as impRenderStyles, uninitialized} from '../core_private'; import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata'; import {assetUrl} from './util'; @@ -98,6 +98,16 @@ export class Identifiers { moduleUrl: assetUrl('core', 'linker/template_ref'), runtime: impTemplateRef_ }); + static CodegenComponentFactoryResolver = new CompileIdentifierMetadata({ + name: 'CodegenComponentFactoryResolver', + moduleUrl: assetUrl('core', 'linker/component_factory_resolver'), + runtime: CodegenComponentFactoryResolver + }); + static ComponentFactoryResolver = new CompileIdentifierMetadata({ + name: 'ComponentFactoryResolver', + moduleUrl: assetUrl('core', 'linker/component_factory_resolver'), + runtime: ComponentFactoryResolver + }); static ValueUnwrapper = new CompileIdentifierMetadata( {name: 'ValueUnwrapper', moduleUrl: CD_MODULE_URL, runtime: impValueUnwrapper}); static Injector = new CompileIdentifierMetadata( diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index f13417bdb6..51040e844a 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -99,6 +99,7 @@ export class CompileMetadataResolver { var changeDetectionStrategy: any /** TODO #9100 */ = null; var viewProviders: any[] /** TODO #9100 */ = []; var moduleUrl = staticTypeModuleUrl(directiveType); + var precompileTypes: cpl.CompileTypeMetadata[] = []; if (dirMeta instanceof ComponentMetadata) { assertArrayOfStrings('styles', dirMeta.styles); var cmpMeta = dirMeta; @@ -124,6 +125,10 @@ export class CompileMetadataResolver { verifyNonBlankProviders(directiveType, dirMeta.viewProviders, 'viewProviders')); } moduleUrl = componentModuleUrl(this._reflector, directiveType, cmpMeta); + if (cmpMeta.precompile) { + precompileTypes = flattenArray(cmpMeta.precompile) + .map((cmp) => this.getTypeMetadata(cmp, staticTypeModuleUrl(cmp))); + } } var providers: any[] /** TODO #9100 */ = []; @@ -152,7 +157,8 @@ export class CompileMetadataResolver { providers: providers, viewProviders: viewProviders, queries: queries, - viewQueries: viewQueries + viewQueries: viewQueries, + precompile: precompileTypes }); this._directiveCache.set(directiveType, meta); } @@ -422,7 +428,7 @@ function flattenPipes(view: ViewMetadata, platformPipes: any[]): Type[] { return pipes; } -function flattenArray(tree: any[], out: Array): void { +function flattenArray(tree: any[], out: Array = []): Array { for (var i = 0; i < tree.length; i++) { var item = resolveForwardRef(tree[i]); if (isArray(item)) { @@ -431,6 +437,7 @@ function flattenArray(tree: any[], out: Array): void { out.push(item); } } + return out; } function verifyNonBlankProviders( diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 53dc9aaf6e..c30e59f8eb 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -17,7 +17,7 @@ import * as o from './output/output_ast'; import {StyleCompiler, StylesCompileResult} from './style_compiler'; import {TemplateParser} from './template_parser'; import {assetUrl} from './util'; -import {ViewCompileResult, ViewCompiler} from './view_compiler/view_compiler'; +import {ComponentFactoryDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; import {XHR} from './xhr'; var _COMPONENT_FACTORY_IDENTIFIER = new CompileIdentifierMetadata({ @@ -57,7 +57,7 @@ export class OfflineCompiler { } var statements: o.DeclareVarStmt[] = []; var exportedVars: string[] = []; - var moduleUrl = _templateModuleUrl(components[0].component); + var moduleUrl = _ngfactoryModuleUrl(components[0].component.type); components.forEach(componentWithDirs => { var compMeta = componentWithDirs.component; _assertComponent(compMeta); @@ -67,7 +67,7 @@ export class OfflineCompiler { var hostMeta = createHostComponentMeta(compMeta.type, compMeta.selector); var hostViewFactoryVar = this._compileComponent(hostMeta, [compMeta], [], statements); - var compFactoryVar = `${compMeta.type.name}NgFactory`; + var compFactoryVar = _componentFactoryName(compMeta.type); statements.push( o.variable(compFactoryVar) .set(o.importExpr(_COMPONENT_FACTORY_IDENTIFIER, [o.importType(compMeta.type)]) @@ -129,8 +129,14 @@ export class OfflineCompiler { } function _resolveViewStatements(compileResult: ViewCompileResult): o.Statement[] { - compileResult.dependencies.forEach( - (dep) => { dep.factoryPlaceholder.moduleUrl = _templateModuleUrl(dep.comp); }); + compileResult.dependencies.forEach((dep) => { + if (dep instanceof ViewFactoryDependency) { + dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp.type); + } else if (dep instanceof ComponentFactoryDependency) { + dep.placeholder.name = _componentFactoryName(dep.comp); + dep.placeholder.moduleUrl = _ngfactoryModuleUrl(dep.comp); + } + }); return compileResult.statements; } @@ -145,11 +151,15 @@ function _resolveStyleStatements( return compileResult.statements; } -function _templateModuleUrl(comp: CompileDirectiveMetadata): string { - var urlWithSuffix = _splitSuffix(comp.type.moduleUrl); +function _ngfactoryModuleUrl(comp: CompileIdentifierMetadata): string { + var urlWithSuffix = _splitSuffix(comp.moduleUrl); return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`; } +function _componentFactoryName(comp: CompileIdentifierMetadata): string { + return `${comp.name}NgFactory`; +} + function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string { return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`; } diff --git a/modules/@angular/compiler/src/runtime_compiler.ts b/modules/@angular/compiler/src/runtime_compiler.ts index 8126753452..d9275b65a8 100644 --- a/modules/@angular/compiler/src/runtime_compiler.ts +++ b/modules/@angular/compiler/src/runtime_compiler.ts @@ -16,7 +16,7 @@ import {PromiseWrapper} from '../src/facade/async'; import {createHostComponentMeta, CompileDirectiveMetadata, CompilePipeMetadata, CompileIdentifierMetadata} from './compile_metadata'; import {TemplateAst,} from './template_ast'; import {StyleCompiler, StylesCompileDependency, StylesCompileResult} from './style_compiler'; -import {ViewCompiler} from './view_compiler/view_compiler'; +import {ViewCompiler, ViewFactoryDependency, ComponentFactoryDependency} from './view_compiler/view_compiler'; import {TemplateParser} from './template_parser'; import {DirectiveNormalizer} from './directive_normalizer'; import {CompileMetadataResolver} from './metadata_resolver'; @@ -37,7 +37,6 @@ export class RuntimeCompiler implements ComponentResolver { private _styleCache: Map> = new Map>(); private _hostCacheKeys = new Map(); private _compiledTemplateCache = new Map(); - private _compiledTemplateDone = new Map>(); constructor( private _metadataResolver: CompileMetadataResolver, @@ -50,43 +49,38 @@ export class RuntimeCompiler implements ComponentResolver { return PromiseWrapper.reject( new BaseException(`Cannot resolve component using '${component}'.`), null); } + return this._loadAndCompileHostComponent(component).done; + } - let componentType = component; + clearCache(): void { + this._styleCache.clear(); + this._compiledTemplateCache.clear(); + this._hostCacheKeys.clear(); + } + + private _loadAndCompileHostComponent(componentType: Type): CompileHostTemplate { var compMeta: CompileDirectiveMetadata = this._metadataResolver.getDirectiveMetadata(componentType); - var hostCacheKey = this._hostCacheKeys.get(componentType); + var hostCacheKey = this._hostCacheKeys.get(compMeta.type.runtime); if (isBlank(hostCacheKey)) { hostCacheKey = new Object(); - this._hostCacheKeys.set(componentType, hostCacheKey); + this._hostCacheKeys.set(compMeta.type.runtime, hostCacheKey); assertComponent(compMeta); var hostMeta: CompileDirectiveMetadata = createHostComponentMeta(compMeta.type, compMeta.selector); this._loadAndCompileComponent(hostCacheKey, hostMeta, [compMeta], [], []); } - return this._compiledTemplateDone.get(hostCacheKey) - .then( - (compiledTemplate: CompiledTemplate) => new ComponentFactory( - compMeta.selector, compiledTemplate.viewFactory, componentType)); + var compTemplate = this._compiledTemplateCache.get(hostCacheKey); + return new CompileHostTemplate(compTemplate, compMeta); } - clearCache(): void { - this._styleCache.clear(); - this._compiledTemplateCache.clear(); - this._compiledTemplateDone.clear(); - this._hostCacheKeys.clear(); - } - - private _loadAndCompileComponent( cacheKey: any, compMeta: CompileDirectiveMetadata, viewDirectives: CompileDirectiveMetadata[], pipes: CompilePipeMetadata[], compilingComponentsPath: any[]): CompiledTemplate { var compiledTemplate = this._compiledTemplateCache.get(cacheKey); - var done = this._compiledTemplateDone.get(cacheKey); if (isBlank(compiledTemplate)) { - compiledTemplate = new CompiledTemplate(); - this._compiledTemplateCache.set(cacheKey, compiledTemplate); - done = + let done = PromiseWrapper .all([this._compileComponentStyles(compMeta)].concat(viewDirectives.map( dirMeta => this._templateNormalizer.normalizeDirective(dirMeta)))) @@ -103,7 +97,8 @@ export class RuntimeCompiler implements ComponentResolver { childPromises)); return PromiseWrapper.all(childPromises).then((_) => { return compiledTemplate; }); }); - this._compiledTemplateDone.set(cacheKey, done); + compiledTemplate = new CompiledTemplate(done); + this._compiledTemplateCache.set(cacheKey, compiledTemplate); } return compiledTemplate; } @@ -116,26 +111,32 @@ export class RuntimeCompiler implements ComponentResolver { compMeta, parsedTemplate, new ir.ExternalExpr(new CompileIdentifierMetadata({runtime: styles})), pipes); compileResult.dependencies.forEach((dep) => { - var childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath); + if (dep instanceof ViewFactoryDependency) { + let childCompilingComponentsPath = ListWrapper.clone(compilingComponentsPath); + let childCacheKey = dep.comp.type.runtime; + let childViewDirectives: CompileDirectiveMetadata[] = + this._metadataResolver.getViewDirectivesMetadata(dep.comp.type.runtime); + let childViewPipes: CompilePipeMetadata[] = + this._metadataResolver.getViewPipesMetadata(dep.comp.type.runtime); + let childIsRecursive = childCompilingComponentsPath.indexOf(childCacheKey) > -1 || + childViewDirectives.some( + dir => childCompilingComponentsPath.indexOf(dir.type.runtime) > -1); + childCompilingComponentsPath.push(childCacheKey); - var childCacheKey = dep.comp.type.runtime; - var childViewDirectives: CompileDirectiveMetadata[] = - this._metadataResolver.getViewDirectivesMetadata(dep.comp.type.runtime); - var childViewPipes: CompilePipeMetadata[] = - this._metadataResolver.getViewPipesMetadata(dep.comp.type.runtime); - var childIsRecursive = childCompilingComponentsPath.indexOf(childCacheKey) > -1 || - childViewDirectives.some( - dir => childCompilingComponentsPath.indexOf(dir.type.runtime) > -1); - childCompilingComponentsPath.push(childCacheKey); - - var childComp = this._loadAndCompileComponent( - dep.comp.type.runtime, dep.comp, childViewDirectives, childViewPipes, - childCompilingComponentsPath); - dep.factoryPlaceholder.runtime = childComp.proxyViewFactory; - dep.factoryPlaceholder.name = `viewFactory_${dep.comp.type.name}`; - if (!childIsRecursive) { - // Only wait for a child if it is not a cycle - childPromises.push(this._compiledTemplateDone.get(childCacheKey)); + let childComp = this._loadAndCompileComponent( + dep.comp.type.runtime, dep.comp, childViewDirectives, childViewPipes, + childCompilingComponentsPath); + dep.placeholder.runtime = childComp.proxyViewFactory; + dep.placeholder.name = `viewFactory_${dep.comp.type.name}`; + if (!childIsRecursive) { + // Only wait for a child if it is not a cycle + childPromises.push(childComp.done); + } + } else if (dep instanceof ComponentFactoryDependency) { + let childComp = this._loadAndCompileHostComponent(dep.comp.runtime); + dep.placeholder.runtime = childComp.componentFactory; + dep.placeholder.name = `compFactory_${dep.comp.name}`; + childPromises.push(childComp.done); } }); var factory: any; @@ -198,16 +199,27 @@ export class RuntimeCompiler implements ComponentResolver { } } +class CompileHostTemplate { + componentFactory: ComponentFactory; + done: Promise>; + constructor(_template: CompiledTemplate, compMeta: CompileDirectiveMetadata) { + this.componentFactory = new ComponentFactory( + compMeta.selector, _template.proxyViewFactory, compMeta.type.runtime); + this.done = _template.done.then((_) => this.componentFactory); + } +} + class CompiledTemplate { - viewFactory: Function = null; + private _viewFactory: Function = null; proxyViewFactory: Function; - constructor() { + constructor(public done: Promise) { this.proxyViewFactory = (viewUtils: any /** TODO #9100 */, childInjector: any /** TODO #9100 */, - contextEl: any /** TODO #9100 */) => this.viewFactory(viewUtils, childInjector, contextEl); + contextEl: any /** TODO #9100 */) => + this._viewFactory(viewUtils, childInjector, contextEl); } - init(viewFactory: Function) { this.viewFactory = viewFactory; } + init(viewFactory: Function) { this._viewFactory = viewFactory; } } function assertComponent(meta: CompileDirectiveMetadata) { diff --git a/modules/@angular/compiler/src/view_compiler/compile_element.ts b/modules/@angular/compiler/src/view_compiler/compile_element.ts index d43022e269..5427f046a8 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_element.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_element.ts @@ -91,6 +91,28 @@ export class CompileElement extends CompileNode { this._instances.add(identifierToken(Identifiers.AppElement), this.appElement); } + public createComponentFactoryResolver(precompileComponent: CompileIdentifierMetadata[]) { + if (!precompileComponent || precompileComponent.length === 0) { + return; + } + var createComponentFactoryResolverExpr = + o.importExpr(Identifiers.CodegenComponentFactoryResolver).instantiate([ + o.literalArr(precompileComponent.map( + (precompiledComponent) => o.importExpr(precompiledComponent))), + injectFromViewParentInjector(identifierToken(Identifiers.ComponentFactoryResolver), false) + ]); + var provider = new CompileProviderMetadata({ + token: identifierToken(Identifiers.ComponentFactoryResolver), + useValue: createComponentFactoryResolverExpr + }); + // Add ComponentFactoryResolver as first provider as it does not have deps on other providers + // ProviderAstType.PrivateService as only the component and its view can see it, + // but nobody else + this._resolvedProvidersArray.unshift(new ProviderAst( + provider.token, false, true, [provider], ProviderAstType.PrivateService, + this.sourceAst.sourceSpan)); + } + setComponentView(compViewExpr: o.Expression) { this._compViewExpr = compViewExpr; this.contentNodesByNgContentIndex = @@ -167,21 +189,20 @@ export class CompileElement extends CompileNode { queriesWithReads, queriesForProvider.map(query => new _QueryWithRead(query, resolvedProvider.token))); }); - StringMapWrapper.forEach( - this.referenceTokens, (_: any /** TODO #9100 */, varName: any /** TODO #9100 */) => { - var token = this.referenceTokens[varName]; - var varValue: any /** TODO #9100 */; - if (isPresent(token)) { - varValue = this._instances.get(token); - } else { - varValue = this.renderNode; - } - this.view.locals.set(varName, varValue); - var varToken = new CompileTokenMetadata({value: varName}); - ListWrapper.addAll( - queriesWithReads, - this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken))); - }); + StringMapWrapper.forEach(this.referenceTokens, (_: CompileTokenMetadata, varName: string) => { + var token = this.referenceTokens[varName]; + var varValue: o.Expression; + if (isPresent(token)) { + varValue = this._instances.get(token); + } else { + varValue = this.renderNode; + } + this.view.locals.set(varName, varValue); + var varToken = new CompileTokenMetadata({value: varName}); + ListWrapper.addAll( + queriesWithReads, + this._getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken))); + }); queriesWithReads.forEach((queryWithRead) => { var value: o.Expression; if (isPresent(queryWithRead.read.identifier)) { @@ -285,7 +306,7 @@ export class CompileElement extends CompileNode { private _getLocalDependency( requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { - var result: any /** TODO #9100 */ = null; + var result: o.Expression = null; // constructor content query if (isBlank(result) && isPresent(dep.query)) { result = this._addQuery(dep.query, null).queryList; @@ -330,7 +351,7 @@ export class CompileElement extends CompileNode { private _getDependency(requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { var currElement: CompileElement = this; - var result: any /** TODO #9100 */ = null; + var result: o.Expression = null; if (dep.isValue) { result = o.literal(dep.value); } @@ -357,7 +378,7 @@ export class CompileElement extends CompileNode { function createInjectInternalCondition( nodeIndex: number, childNodeCount: number, provider: ProviderAst, providerExpr: o.Expression): o.Statement { - var indexCondition: any /** TODO #9100 */; + var indexCondition: o.Expression; if (childNodeCount > 0) { indexCondition = o.literal(nodeIndex) .lowerEquals(InjectMethodVars.requestNodeIndex) @@ -375,8 +396,8 @@ function createProviderProperty( propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression { var view = compileElement.view; - var resolvedProviderValueExpr: any /** TODO #9100 */; - var type: any /** TODO #9100 */; + var resolvedProviderValueExpr: o.Expression; + var type: o.Type; if (isMulti) { resolvedProviderValueExpr = o.literalArr(providerValueExpressions); type = new o.ArrayType(o.DYNAMIC_TYPE); @@ -421,8 +442,8 @@ class _ValueOutputAstTransformer extends ValueTransformer { return o.literalArr(arr.map(value => visitValue(value, this, context))); } visitStringMap(map: {[key: string]: any}, context: any): o.Expression { - var entries: any[] /** TODO #9100 */ = []; - StringMapWrapper.forEach(map, (value: any /** TODO #9100 */, key: any /** TODO #9100 */) => { + var entries: Array[] = []; + StringMapWrapper.forEach(map, (value: any, key: string) => { entries.push([key, visitValue(value, this, context)]); }); return o.literalMap(entries); diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 4224487a39..7580cc6d52 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -9,22 +9,18 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; import {ViewType, isDefaultChangeDetectionStrategy} from '../../core_private'; +import {AnimationCompiler} from '../animation/animation_compiler'; +import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileTokenMetadata, CompileTypeMetadata} from '../compile_metadata'; import {ListWrapper, SetWrapper, StringMapWrapper} from '../facade/collection'; import {StringWrapper, isPresent} from '../facade/lang'; import {Identifiers, identifierToken} from '../identifiers'; import * as o from '../output/output_ast'; +import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ProviderAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_ast'; import {CompileElement, CompileNode} from './compile_element'; import {CompileView} from './compile_view'; import {ChangeDetectionStrategyEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants'; - -import {TemplateAst, TemplateAstVisitor, NgContentAst, EmbeddedTemplateAst, ElementAst, ReferenceAst, VariableAst, BoundEventAst, BoundElementPropertyAst, AttrAst, BoundTextAst, TextAst, DirectiveAst, BoundDirectivePropertyAst, templateVisitAll,} from '../template_ast'; - -import {getViewFactoryName, createFlatArray, createDiTokenExpression} from './util'; - -import {CompileIdentifierMetadata, CompileDirectiveMetadata, CompileTokenMetadata} from '../compile_metadata'; - -import {AnimationCompiler} from '../animation/animation_compiler'; +import {createDiTokenExpression, createFlatArray, getViewFactoryName} from './util'; const IMPLICIT_TEMPLATE_VAR = '\$implicit'; const CLASS_ATTR = 'class'; @@ -34,15 +30,20 @@ const NG_CONTAINER_TAG = 'ng-container'; var parentRenderNodeVar = o.variable('parentRenderNode'); var rootSelectorVar = o.variable('rootSelector'); -export class ViewCompileDependency { +export class ViewFactoryDependency { constructor( - public comp: CompileDirectiveMetadata, public factoryPlaceholder: CompileIdentifierMetadata) { - } + public comp: CompileDirectiveMetadata, public placeholder: CompileIdentifierMetadata) {} } +export class ComponentFactoryDependency { + constructor( + public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {} +} + + export function buildView( view: CompileView, template: TemplateAst[], - targetDependencies: ViewCompileDependency[]): number { + targetDependencies: Array): number { var builderVisitor = new ViewBuilderVisitor(view, targetDependencies); templateVisitAll( builderVisitor, template, @@ -65,7 +66,9 @@ class ViewBuilderVisitor implements TemplateAstVisitor { private _animationCompiler = new AnimationCompiler(); - constructor(public view: CompileView, public targetDependencies: ViewCompileDependency[]) {} + constructor( + public view: CompileView, + public targetDependencies: Array) {} private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; } @@ -203,9 +206,17 @@ class ViewBuilderVisitor implements TemplateAstVisitor { this.view.nodes.push(compileElement); var compViewExpr: o.ReadVarExpr = null; if (isPresent(component)) { - var nestedComponentIdentifier = + let nestedComponentIdentifier = new CompileIdentifierMetadata({name: getViewFactoryName(component, 0)}); - this.targetDependencies.push(new ViewCompileDependency(component, nestedComponentIdentifier)); + this.targetDependencies.push(new ViewFactoryDependency(component, nestedComponentIdentifier)); + let precompileComponentIdentifiers = + component.precompile.map((precompileComp: CompileIdentifierMetadata) => { + var id = new CompileIdentifierMetadata({name: precompileComp.name}); + this.targetDependencies.push(new ComponentFactoryDependency(precompileComp, id)); + return id; + }); + compileElement.createComponentFactoryResolver(precompileComponentIdentifiers); + compViewExpr = o.variable(`compView_${nodeIndex}`); // fix highlighting: ` compileElement.setComponentView(compViewExpr); this.view.createMethod.addStmt( diff --git a/modules/@angular/compiler/src/view_compiler/view_compiler.ts b/modules/@angular/compiler/src/view_compiler/view_compiler.ts index 6edf63a2ff..d43c22a494 100644 --- a/modules/@angular/compiler/src/view_compiler/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler/view_compiler.ts @@ -17,12 +17,14 @@ import {TemplateAst} from '../template_ast'; import {CompileElement} from './compile_element'; import {CompileView} from './compile_view'; import {bindView} from './view_binder'; -import {ViewCompileDependency, buildView, finishView} from './view_builder'; +import {ComponentFactoryDependency, ViewFactoryDependency, buildView, finishView} from './view_builder'; + +export {ComponentFactoryDependency, ViewFactoryDependency} from './view_builder'; export class ViewCompileResult { constructor( public statements: o.Statement[], public viewFactoryVar: string, - public dependencies: ViewCompileDependency[]) {} + public dependencies: Array) {} } @Injectable() @@ -33,9 +35,9 @@ export class ViewCompiler { compileComponent( component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, pipes: CompilePipeMetadata[]): ViewCompileResult { - var dependencies: any[] /** TODO #9100 */ = []; + var dependencies: Array = []; var compiledAnimations = this._animationCompiler.compileComponent(component); - var statements: any[] /** TODO #9100 */ = []; + var statements: o.Statement[] = []; compiledAnimations.map(entry => { statements.push(entry.statesMapStatement); statements.push(entry.fnStatement); diff --git a/modules/@angular/compiler/testing/directive_resolver_mock.ts b/modules/@angular/compiler/testing/directive_resolver_mock.ts index 735919ad51..edb682a711 100644 --- a/modules/@angular/compiler/testing/directive_resolver_mock.ts +++ b/modules/@angular/compiler/testing/directive_resolver_mock.ts @@ -51,7 +51,8 @@ export class MockDirectiveResolver extends DirectiveResolver { queries: dm.queries, changeDetection: dm.changeDetection, providers: providers, - viewProviders: viewProviders + viewProviders: viewProviders, + precompile: dm.precompile }); } diff --git a/modules/@angular/core/private_export.ts b/modules/@angular/core/private_export.ts index bdd2f2772a..8854f53e9b 100644 --- a/modules/@angular/core/private_export.ts +++ b/modules/@angular/core/private_export.ts @@ -21,6 +21,7 @@ import * as console from './src/console'; import * as debug from './src/debug/debug_renderer'; import * as provider_util from './src/di/provider_util'; import * as reflective_provider from './src/di/reflective_provider'; +import * as component_factory_resolver from './src/linker/component_factory_resolver'; import * as component_resolver from './src/linker/component_resolver'; import * as debug_context from './src/linker/debug_context'; import * as element from './src/linker/element'; @@ -52,6 +53,8 @@ export declare namespace __core_private_types__ { export type ReflectorReader = reflector_reader.ReflectorReader; export var ReflectorReader: typeof reflector_reader.ReflectorReader; export var ReflectorComponentResolver: typeof component_resolver.ReflectorComponentResolver; + export var CodegenComponentFactoryResolver: + typeof component_factory_resolver.CodegenComponentFactoryResolver; export type AppElement = element.AppElement; export var AppElement: typeof element.AppElement; export var AppView: typeof view.AppView; @@ -141,6 +144,7 @@ export var __core_private__ = { LIFECYCLE_HOOKS_VALUES: lifecycle_hooks.LIFECYCLE_HOOKS_VALUES, ReflectorReader: reflector_reader.ReflectorReader, ReflectorComponentResolver: component_resolver.ReflectorComponentResolver, + CodegenComponentFactoryResolver: component_factory_resolver.CodegenComponentFactoryResolver, AppElement: element.AppElement, AppView: view.AppView, DebugAppView: view.DebugAppView, diff --git a/modules/@angular/core/src/application_common_providers.ts b/modules/@angular/core/src/application_common_providers.ts index d868b43297..4081b2eb32 100644 --- a/modules/@angular/core/src/application_common_providers.ts +++ b/modules/@angular/core/src/application_common_providers.ts @@ -11,6 +11,7 @@ import {Type} from '../src/facade/lang'; import {APPLICATION_CORE_PROVIDERS} from './application_ref'; import {APP_ID_RANDOM_PROVIDER} from './application_tokens'; import {IterableDiffers, KeyValueDiffers, defaultIterableDiffers, defaultKeyValueDiffers} from './change_detection/change_detection'; +import {ComponentFactoryResolver} from './linker/component_factory_resolver'; import {ComponentResolver, ReflectorComponentResolver} from './linker/component_resolver'; import {DynamicComponentLoader, DynamicComponentLoader_} from './linker/dynamic_component_loader'; import {ViewUtils} from './linker/view_utils'; @@ -26,6 +27,7 @@ export const APPLICATION_COMMON_PROVIDERS: Array /*@ts2dart_const*/[ APPLICATION_CORE_PROVIDERS, /* @ts2dart_Provider */ {provide: ComponentResolver, useClass: ReflectorComponentResolver}, + {provide: ComponentFactoryResolver, useValue: ComponentFactoryResolver.NULL}, APP_ID_RANDOM_PROVIDER, ViewUtils, /* @ts2dart_Provider */ {provide: IterableDiffers, useValue: defaultIterableDiffers}, diff --git a/modules/@angular/core/src/linker.ts b/modules/@angular/core/src/linker.ts index d0fb1c68d6..640e932c4e 100644 --- a/modules/@angular/core/src/linker.ts +++ b/modules/@angular/core/src/linker.ts @@ -8,6 +8,7 @@ // Public API for compiler export {ComponentFactory, ComponentRef} from './linker/component_factory'; +export {ComponentFactoryResolver, NoComponentFactoryError} from './linker/component_factory_resolver'; export {ComponentResolver} from './linker/component_resolver'; export {DynamicComponentLoader} from './linker/dynamic_component_loader'; export {ElementRef} from './linker/element_ref'; diff --git a/modules/@angular/core/src/linker/component_factory_resolver.ts b/modules/@angular/core/src/linker/component_factory_resolver.ts new file mode 100644 index 0000000000..3de1854ea7 --- /dev/null +++ b/modules/@angular/core/src/linker/component_factory_resolver.ts @@ -0,0 +1,49 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Inject, OpaqueToken, Optional, SkipSelf} from '../di'; +import {BaseException} from '../facade/exceptions'; +import {ClassWithConstructor, stringify} from '../facade/lang'; + +import {ComponentFactory} from './component_factory'; + +export class NoComponentFactoryError extends BaseException { + constructor(public component: Function) { + super(`No component factory found for ${stringify(component)}`); + } +} + +class _NullComponentFactoryResolver implements ComponentFactoryResolver { + resolveComponentFactory(component: {new (...args: any[]): T}): ComponentFactory { + throw new NoComponentFactoryError(component); + } +} + +export abstract class ComponentFactoryResolver { + static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver(); + abstract resolveComponentFactory(component: ClassWithConstructor): ComponentFactory; +} + +export class CodegenComponentFactoryResolver implements ComponentFactoryResolver { + private _factories = new Map>(); + + constructor(factories: ComponentFactory[], private _parent: ComponentFactoryResolver) { + for (let i = 0; i < factories.length; i++) { + let factory = factories[i]; + this._factories.set(factory.componentType, factory); + } + } + + resolveComponentFactory(component: {new (...args: any[]): T}): ComponentFactory { + let result = this._factories.get(component); + if (!result) { + result = this._parent.resolveComponentFactory(component); + } + return result; + } +} diff --git a/modules/@angular/core/src/metadata.ts b/modules/@angular/core/src/metadata.ts index b2b34cd50f..46c59699f1 100644 --- a/modules/@angular/core/src/metadata.ts +++ b/modules/@angular/core/src/metadata.ts @@ -186,7 +186,8 @@ export interface ComponentMetadataFactory { directives?: Array, pipes?: Array, encapsulation?: ViewEncapsulation, - interpolation?: [string, string] + interpolation?: [string, string], + precompile?: Array }): ComponentDecorator; new (obj: { selector?: string, @@ -209,7 +210,8 @@ export interface ComponentMetadataFactory { directives?: Array, pipes?: Array, encapsulation?: ViewEncapsulation, - interpolation?: [string, string] + interpolation?: [string, string], + precompile?: Array }): ComponentMetadata; } diff --git a/modules/@angular/core/src/metadata/directives.ts b/modules/@angular/core/src/metadata/directives.ts index 9d206b09c8..df48963674 100644 --- a/modules/@angular/core/src/metadata/directives.ts +++ b/modules/@angular/core/src/metadata/directives.ts @@ -964,6 +964,14 @@ export class ComponentMetadata extends DirectiveMetadata { interpolation: [string, string]; + /** + * Defines the components that should be precompiled as well when + * this component is defined. For each components listed here, + * Angular will create a {@link ComponentFactory ComponentFactory} and store it in the + * {@link ComponentFactoryResolver ComponentFactoryResolver}. + */ + precompile: Array; + constructor({selector, inputs, outputs, @@ -984,7 +992,8 @@ export class ComponentMetadata extends DirectiveMetadata { directives, pipes, encapsulation, - interpolation}: { + interpolation, + precompile}: { selector?: string, inputs?: string[], outputs?: string[], @@ -1005,7 +1014,8 @@ export class ComponentMetadata extends DirectiveMetadata { directives?: Array, pipes?: Array, encapsulation?: ViewEncapsulation, - interpolation?: [string, string] + interpolation?: [string, string], + precompile?: Array } = {}) { super({ selector: selector, @@ -1031,6 +1041,7 @@ export class ComponentMetadata extends DirectiveMetadata { this.moduleId = moduleId; this.animations = animations; this.interpolation = interpolation; + this.precompile = precompile; } } diff --git a/modules/@angular/core/test/linker/precompile_integration_spec.ts b/modules/@angular/core/test/linker/precompile_integration_spec.ts new file mode 100644 index 0000000000..aa6305bce2 --- /dev/null +++ b/modules/@angular/core/test/linker/precompile_integration_spec.ts @@ -0,0 +1,100 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {beforeEach, ddescribe, xdescribe, describe, expect, iit, inject, beforeEachProviders, it, xit,} from '@angular/core/testing/testing_internal'; +import {TestComponentBuilder} from '@angular/compiler/testing'; +import {AsyncTestCompleter} from '@angular/core/testing/testing_internal'; +import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; +import {Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, NoComponentFactoryError, ComponentRef, forwardRef} from '@angular/core'; +import {CompilerConfig} from '@angular/compiler'; + +export function main() { + describe('jit', () => { declareTests({useJit: true}); }); + describe('no jit', () => { declareTests({useJit: false}); }); +} + +function declareTests({useJit}: {useJit: boolean}) { + describe('@Component.precompile', function() { + it('should resolve ComponentFactories from the same component', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + tcb.createAsync(MainComp).then((compFixture) => { + let mainComp: MainComp = compFixture.componentInstance; + expect(compFixture.debugElement.injector.get(ComponentFactoryResolver)) + .toBe(mainComp.cfr); + var cf = mainComp.cfr.resolveComponentFactory(ChildComp); + expect(cf.componentType).toBe(ChildComp); + async.done(); + }); + })); + + it('should be able to get a component form a parent component (view hiearchy)', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + tcb.overrideTemplate(MainComp, '') + .createAsync(MainComp) + .then((compFixture) => { + let childCompEl = compFixture.debugElement.children[0]; + let childComp: ChildComp = childCompEl.componentInstance; + // declared on ChildComp directly + expect(childComp.cfr.resolveComponentFactory(NestedChildComp).componentType) + .toBe(NestedChildComp); + // inherited from MainComp + expect(childComp.cfr.resolveComponentFactory(ChildComp).componentType) + .toBe(ChildComp); + async.done(); + }); + })); + + it('should not be able to get components from a parent component (content hierarchy)', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + tcb.overrideTemplate(MainComp, '') + .overrideTemplate(ChildComp, '') + .createAsync(MainComp) + .then((compFixture) => { + let nestedChildCompEl = compFixture.debugElement.children[0].children[0]; + let nestedChildComp: NestedChildComp = nestedChildCompEl.componentInstance; + expect(nestedChildComp.cfr.resolveComponentFactory(ChildComp).componentType) + .toBe(ChildComp); + expect(() => nestedChildComp.cfr.resolveComponentFactory(NestedChildComp)) + .toThrow(new NoComponentFactoryError(NestedChildComp)); + async.done(); + }); + })); + }); +} + +var DIRECTIVES: any[] = [ + forwardRef(() => NestedChildComp), + forwardRef(() => ChildComp), + forwardRef(() => MainComp), +]; + +@Component({selector: 'nested', directives: DIRECTIVES, template: ''}) +class NestedChildComp { + constructor(public cfr: ComponentFactoryResolver) {} +} + +@Component({selector: 'child', precompile: [NestedChildComp], directives: DIRECTIVES, template: ''}) +class ChildComp { + constructor(public cfr: ComponentFactoryResolver) {} +} + +@Component({ + selector: 'main', + precompile: [ChildComp], + directives: DIRECTIVES, + template: '', +}) +class MainComp { + constructor(public cfr: ComponentFactoryResolver) {} +} diff --git a/modules/@angular/facade/src/lang.ts b/modules/@angular/facade/src/lang.ts index 03d288c157..9af485edfb 100644 --- a/modules/@angular/facade/src/lang.ts +++ b/modules/@angular/facade/src/lang.ts @@ -74,6 +74,8 @@ export interface Type extends Function {} */ export interface ConcreteType extends Type { new (...args: any[] /** TODO #9100 */): any; } +export interface ClassWithConstructor { new (...args: any[]): T; } + export function getTypeNameForDebugging(type: Type): string { if (type['name']) { return type['name']; diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index 8e4c4eb722..a83e37f5e2 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -137,15 +137,19 @@ const CORE = [ 'ComponentFactory.create(injector:Injector, projectableNodes:any[][]=null, rootSelectorOrNode:string|any=null):ComponentRef', 'ComponentFactory.selector:string', 'ComponentFactory', + 'ComponentFactoryResolver', + 'ComponentFactoryResolver.NULL:ComponentFactoryResolver', + 'ComponentFactoryResolver.resolveComponentFactory(component:ClassWithConstructor):ComponentFactory', 'ComponentMetadata', 'ComponentMetadata.animations:AnimationEntryMetadata[]', 'ComponentMetadata.changeDetection:ChangeDetectionStrategy', - 'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,animations,directives,pipes,encapsulation,interpolation}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], animations?:AnimationEntryMetadata[], directives?:Array, pipes?:Array, encapsulation?:ViewEncapsulation, interpolation?:[string, string]}={})', + 'ComponentMetadata.constructor({selector,inputs,outputs,properties,events,host,exportAs,moduleId,providers,viewProviders,changeDetection=ChangeDetectionStrategy.Default,queries,templateUrl,template,styleUrls,styles,animations,directives,pipes,encapsulation,interpolation,precompile}:{selector?:string, inputs?:string[], outputs?:string[], properties?:string[], events?:string[], host?:{[key:string]:string}, providers?:any[], exportAs?:string, moduleId?:string, viewProviders?:any[], queries?:{[key:string]:any}, changeDetection?:ChangeDetectionStrategy, templateUrl?:string, template?:string, styleUrls?:string[], styles?:string[], animations?:AnimationEntryMetadata[], directives?:Array, pipes?:Array, encapsulation?:ViewEncapsulation, interpolation?:[string, string], precompile?:Array}={})', 'ComponentMetadata.directives:Array', 'ComponentMetadata.encapsulation:ViewEncapsulation', 'ComponentMetadata.interpolation:[string, string]', 'ComponentMetadata.moduleId:string', 'ComponentMetadata.pipes:Array', + 'ComponentMetadata.precompile:Array', 'ComponentMetadata.styles:string[]', 'ComponentMetadata.styleUrls:string[]', 'ComponentMetadata.template:string', @@ -366,6 +370,9 @@ const CORE = [ 'NgZoneError.stackTrace:any', 'NoAnnotationError', 'NoAnnotationError.constructor(typeOrFunc:any, params:any[][])', + 'NoComponentFactoryError', + 'NoComponentFactoryError.component:Function', + 'NoComponentFactoryError.constructor(component:Function)', 'NoProviderError', 'NoProviderError.constructor(injector:ReflectiveInjector, key:ReflectiveKey)', 'OnChanges', @@ -1144,8 +1151,8 @@ const COMPILER = [ 'CompileDiDependencyMetadata.viewQuery:CompileQueryMetadata', 'CompileDirectiveMetadata', 'CompileDirectiveMetadata.changeDetection:ChangeDetectionStrategy', - 'CompileDirectiveMetadata.constructor({type,isComponent,selector,exportAs,changeDetection,inputs,outputs,hostListeners,hostProperties,hostAttributes,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:{[key:string]:string}, outputs?:{[key:string]:string}, hostListeners?:{[key:string]:string}, hostProperties?:{[key:string]:string}, hostAttributes?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array, viewProviders?:Array, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata}={})', - 'CompileDirectiveMetadata.create({type,isComponent,selector,exportAs,changeDetection,inputs,outputs,host,lifecycleHooks,providers,viewProviders,queries,viewQueries,template}:{type?:CompileTypeMetadata, isComponent?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:string[], outputs?:string[], host?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array, viewProviders?:Array, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], template?:CompileTemplateMetadata}={}):CompileDirectiveMetadata', + 'CompileDirectiveMetadata.constructor({type,isComponent,selector,exportAs,changeDetection,inputs,outputs,hostListeners,hostProperties,hostAttributes,lifecycleHooks,providers,viewProviders,queries,viewQueries,precompile,template}:{type?:CompileTypeMetadata, isComponent?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:{[key:string]:string}, outputs?:{[key:string]:string}, hostListeners?:{[key:string]:string}, hostProperties?:{[key:string]:string}, hostAttributes?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array, viewProviders?:Array, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], precompile?:CompileTypeMetadata[], template?:CompileTemplateMetadata}={})', + 'CompileDirectiveMetadata.create({type,isComponent,selector,exportAs,changeDetection,inputs,outputs,host,lifecycleHooks,providers,viewProviders,queries,viewQueries,precompile,template}:{type?:CompileTypeMetadata, isComponent?:boolean, selector?:string, exportAs?:string, changeDetection?:ChangeDetectionStrategy, inputs?:string[], outputs?:string[], host?:{[key:string]:string}, lifecycleHooks?:LifecycleHooks[], providers?:Array, viewProviders?:Array, queries?:CompileQueryMetadata[], viewQueries?:CompileQueryMetadata[], precompile?:CompileTypeMetadata[], template?:CompileTemplateMetadata}={}):CompileDirectiveMetadata', 'CompileDirectiveMetadata.exportAs:string', 'CompileDirectiveMetadata.fromJson(data:{[key:string]:any}):CompileDirectiveMetadata', 'CompileDirectiveMetadata.hostAttributes:{[key:string]:string}', @@ -1156,6 +1163,7 @@ const COMPILER = [ 'CompileDirectiveMetadata.isComponent:boolean', 'CompileDirectiveMetadata.lifecycleHooks:LifecycleHooks[]', 'CompileDirectiveMetadata.outputs:{[key:string]:string}', + 'CompileDirectiveMetadata.precompile:CompileTypeMetadata[]', 'CompileDirectiveMetadata.providers:CompileProviderMetadata[]', 'CompileDirectiveMetadata.queries:CompileQueryMetadata[]', 'CompileDirectiveMetadata.selector:string',