diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index 8c326313bb..f85d86face 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -97,7 +97,8 @@ export class AppModule implements Injector, NgModuleRef { this.renderer2 = new DomRendererFactory2(null, null); trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle(''); trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey'); - this.componentFactory = createComponentFactory('#root', TreeComponent, TreeComponent_Host); + this.componentFactory = + createComponentFactory('#root', TreeComponent, TreeComponent_Host, {}, {}, []); } get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { diff --git a/packages/compiler/src/aot/compiler.ts b/packages/compiler/src/aot/compiler.ts index 67ebc6d939..71f51169f5 100644 --- a/packages/compiler/src/aot/compiler.ts +++ b/packages/compiler/src/aot/compiler.ts @@ -162,12 +162,27 @@ export class AotCompiler { hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements) .viewClassVar; const compFactoryVar = componentFactoryName(compMeta.type.reference); + const inputsExprs: o.LiteralMapEntry[] = []; + for (let propName in compMeta.inputs) { + const templateName = compMeta.inputs[propName]; + // Don't quote so that the key gets minified... + inputsExprs.push(new o.LiteralMapEntry(propName, o.literal(templateName), false)); + } + const outputsExprs: o.LiteralMapEntry[] = []; + for (let propName in compMeta.outputs) { + const templateName = compMeta.outputs[propName]; + // Don't quote so that the key gets minified... + outputsExprs.push(new o.LiteralMapEntry(propName, o.literal(templateName), false)); + } + targetStatements.push( o.variable(compFactoryVar) .set(o.importExpr(createIdentifier(Identifiers.createComponentFactory)).callFn([ - o.literal(compMeta.selector), - o.importExpr(compMeta.type), - o.variable(hostViewFactoryVar), + o.literal(compMeta.selector), o.importExpr(compMeta.type), + o.variable(hostViewFactoryVar), new o.LiteralMapExpr(inputsExprs), + new o.LiteralMapExpr(outputsExprs), + o.literalArr( + compMeta.template.ngContentSelectors.map(selector => o.literal(selector))) ])) .toDeclStmt( o.importType( diff --git a/packages/compiler/src/jit/compiler.ts b/packages/compiler/src/jit/compiler.ts index f8bce1ec83..39e5a580bd 100644 --- a/packages/compiler/src/jit/compiler.ts +++ b/packages/compiler/src/jit/compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core'; +import {Compiler, ComponentFactory, Inject, Injector, ModuleWithComponentFactories, NgModuleFactory, Type, ɵConsole as Console, ɵgetComponentViewDefinitionFactory as getComponentViewDefinitionFactory, ɵstringify as stringify} from '@angular/core'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata'; import {CompilerConfig} from '../config'; @@ -44,7 +44,7 @@ export class JitCompiler implements Compiler { private _injector: Injector, private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler, - private _compilerConfig: CompilerConfig) {} + private _compilerConfig: CompilerConfig, private _console: Console) {} get injector(): Injector { return this._injector; } @@ -66,6 +66,8 @@ export class JitCompiler implements Compiler { } getNgContentSelectors(component: Type): string[] { + this._console.warn( + 'Compiler.getNgContentSelectors is deprecated. Use ComponentFactory.ngContentSelectors instead!'); const template = this._compiledTemplateCache.get(component); if (!template) { throw new Error(`The component ${stringify(component)} is not yet compiled!`); diff --git a/packages/compiler/src/metadata_resolver.ts b/packages/compiler/src/metadata_resolver.ts index a8af2ef7f9..106f5f07d4 100644 --- a/packages/compiler/src/metadata_resolver.ts +++ b/packages/compiler/src/metadata_resolver.ts @@ -142,7 +142,26 @@ export class CompileMetadataResolver { ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); } else { const hostView = this.getHostComponentViewClass(dirType); - return createComponentFactory(selector, dirType, hostView); + // Note: inputs / outputs / ngContentSelectors will be filled later once the template is + // loaded. + return createComponentFactory(selector, dirType, hostView, {}, {}, []); + } + } + + private initComponentFactory( + factory: StaticSymbol|ComponentFactory, inputs: {[key: string]: string}, + outputs: {[key: string]: string}, ngContentSelectors: string[]) { + if (!(factory instanceof StaticSymbol)) { + for (let propName in inputs) { + const templateName = inputs[propName]; + factory.inputs.push({propName, templateName}); + } + const outputsArr: {propName: string, templateName: string}[] = []; + for (let propName in outputs) { + const templateName = outputs[propName]; + factory.outputs.push({propName, templateName}); + } + factory.ngContentSelectors.push(...ngContentSelectors); } } @@ -186,6 +205,11 @@ export class CompileMetadataResolver { componentFactory: metadata.componentFactory, template: templateMetadata }); + if (templateMetadata) { + this.initComponentFactory( + metadata.componentFactory, metadata.inputs, metadata.outputs, + templateMetadata.ngContentSelectors); + } this._directiveCache.set(directiveType, normalizedDirMeta); this._summaryCache.set(directiveType, normalizedDirMeta.toSummary()); return normalizedDirMeta; diff --git a/packages/compiler/test/aot/compiler_spec.ts b/packages/compiler/test/aot/compiler_spec.ts index 846fb5a7f9..2dab0973cd 100644 --- a/packages/compiler/test/aot/compiler_spec.ts +++ b/packages/compiler/test/aot/compiler_spec.ts @@ -248,6 +248,55 @@ describe('compiler (unbundled Angular)', () => { `Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`); })); }); + + describe('ComponentFactories', () => { + it('should include inputs, outputs and ng-content selectors in the component factory', + fakeAsync(() => { + const FILES: MockData = { + app: { + 'app.ts': ` + import {Component, NgModule, Input, Output} from '@angular/core'; + + @Component({ + selector: 'my-comp', + template: '' + }) + export class MyComp { + @Input('aInputName') + aInputProp: string; + + @Output('aOutputName') + aOutputProp: any; + } + + @NgModule({ + declarations: [MyComp] + }) + export class MyModule {} + ` + } + }; + const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); + const aotHost = new MockAotCompilerHost(host); + let generatedFiles: GeneratedFile[]; + const warnSpy = spyOn(console, 'warn'); + compile(host, aotHost, expectNoDiagnostics).then((f) => generatedFiles = f); + + tick(); + + const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts'); + const createComponentFactoryCall = + /ɵccf\([^)]*\)/m.exec(genFile.source)[0].replace(/\s*/g, ''); + // selector + expect(createComponentFactoryCall).toContain('my-comp'); + // inputs + expect(createComponentFactoryCall).toContain(`{aInputProp:'aInputName'}`); + // outputs + expect(createComponentFactoryCall).toContain(`{aOutputProp:'aOutputName'}`); + // ngContentSelectors + expect(createComponentFactoryCall).toContain(`['*','child']`); + })); + }); }); describe('compiler (bundled Angular)', () => { diff --git a/packages/core/src/linker/compiler.ts b/packages/core/src/linker/compiler.ts index c521756288..501a1101ba 100644 --- a/packages/core/src/linker/compiler.ts +++ b/packages/core/src/linker/compiler.ts @@ -73,6 +73,8 @@ export class Compiler { * the template of the given component. * This is used by the `upgrade` library to compile the appropriate transclude content * in the AngularJS wrapper component. + * + * @deprecated since v4. Use ComponentFactory.ngContentSelectors instead. */ getNgContentSelectors(component: Type): string[] { throw _throwError(); } diff --git a/packages/core/src/linker/component_factory.ts b/packages/core/src/linker/component_factory.ts index a993802fa3..7e7ac7c519 100644 --- a/packages/core/src/linker/component_factory.ts +++ b/packages/core/src/linker/component_factory.ts @@ -70,6 +70,18 @@ export abstract class ComponentRef { export abstract class ComponentFactory { abstract get selector(): string; abstract get componentType(): Type; + /** + * selector for all elements in the component. + */ + abstract get ngContentSelectors(): string[]; + /** + * the inputs of the component. + */ + abstract get inputs(): {propName: string, templateName: string}[]; + /** + * the outputs of the component. + */ + abstract get outputs(): {propName: string, templateName: string}[]; /** * Creates a new component. */ diff --git a/packages/core/src/linker/component_factory_resolver.ts b/packages/core/src/linker/component_factory_resolver.ts index fad59116f4..6ed25597dd 100644 --- a/packages/core/src/linker/component_factory_resolver.ts +++ b/packages/core/src/linker/component_factory_resolver.ts @@ -65,6 +65,9 @@ export class ComponentFactoryBoundToModule extends ComponentFactory { get selector() { return this.factory.selector; } get componentType() { return this.factory.componentType; } + get ngContentSelectors() { return this.factory.ngContentSelectors; } + get inputs() { return this.factory.inputs; } + get outputs() { return this.factory.outputs; } create( injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, diff --git a/packages/core/src/view/refs.ts b/packages/core/src/view/refs.ts index defbcf1b6f..cd720198dc 100644 --- a/packages/core/src/view/refs.ts +++ b/packages/core/src/view/refs.ts @@ -26,9 +26,21 @@ import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachVi const EMPTY_CONTEXT = new Object(); export function createComponentFactory( - selector: string, componentType: Type, - viewDefFactory: ViewDefinitionFactory): ComponentFactory { - return new ComponentFactory_(selector, componentType, viewDefFactory); + selector: string, componentType: Type, viewDefFactory: ViewDefinitionFactory, + inputs: {[propName: string]: string}, outputs: {[propName: string]: string}, + ngContentSelectors: string[]): ComponentFactory { + const inputsArr: {propName: string, templateName: string}[] = []; + for (let propName in inputs) { + const templateName = inputs[propName]; + inputsArr.push({propName, templateName}); + } + const outputsArr: {propName: string, templateName: string}[] = []; + for (let propName in outputs) { + const templateName = outputs[propName]; + outputsArr.push({propName, templateName}); + } + return new ComponentFactory_( + selector, componentType, viewDefFactory, inputsArr, outputsArr, ngContentSelectors); } export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory): @@ -44,7 +56,10 @@ class ComponentFactory_ extends ComponentFactory { constructor( public selector: string, public componentType: Type, - viewDefFactory: ViewDefinitionFactory) { + viewDefFactory: ViewDefinitionFactory, + public inputs: {propName: string, templateName: string}[], + public outputs: {propName: string, templateName: string}[], + public ngContentSelectors: string[]) { super(); this.viewDefFactory = viewDefFactory; }