feat(core): expose `inputs`, `outputs` and `ngContentSelectors` on `ComponentFactory`.
E.g. for a component like this: ``` @Component({ template: ‘<ng-content select=“child”></ng-content>’ }) class MyComp { @Input(‘aInputName’) aInputProp: string; @Output(‘aEventName’) aOuputProp: EventEmitter<any>; } ``` the `ComponentFactory` will now contain the following: - `inputs = {aInputProp: ‘aInputName’}` - `outputs = {aOutputProp: ‘aOutputName’}` - `ngContentSelectors = [‘child’]`
This commit is contained in:
parent
8e2c8b3e4d
commit
1171f91a80
|
@ -97,7 +97,8 @@ export class AppModule implements Injector, NgModuleRef<any> {
|
||||||
this.renderer2 = new DomRendererFactory2(null, null);
|
this.renderer2 = new DomRendererFactory2(null, null);
|
||||||
trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle('');
|
trustedEmptyColor = this.sanitizer.bypassSecurityTrustStyle('');
|
||||||
trustedGreyColor = this.sanitizer.bypassSecurityTrustStyle('grey');
|
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 {
|
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
||||||
|
|
|
@ -162,12 +162,27 @@ export class AotCompiler {
|
||||||
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements)
|
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements)
|
||||||
.viewClassVar;
|
.viewClassVar;
|
||||||
const compFactoryVar = componentFactoryName(compMeta.type.reference);
|
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(
|
targetStatements.push(
|
||||||
o.variable(compFactoryVar)
|
o.variable(compFactoryVar)
|
||||||
.set(o.importExpr(createIdentifier(Identifiers.createComponentFactory)).callFn([
|
.set(o.importExpr(createIdentifier(Identifiers.createComponentFactory)).callFn([
|
||||||
o.literal(compMeta.selector),
|
o.literal(compMeta.selector), o.importExpr(compMeta.type),
|
||||||
o.importExpr(compMeta.type),
|
o.variable(hostViewFactoryVar), new o.LiteralMapExpr(inputsExprs),
|
||||||
o.variable(hostViewFactoryVar),
|
new o.LiteralMapExpr(outputsExprs),
|
||||||
|
o.literalArr(
|
||||||
|
compMeta.template.ngContentSelectors.map(selector => o.literal(selector)))
|
||||||
]))
|
]))
|
||||||
.toDeclStmt(
|
.toDeclStmt(
|
||||||
o.importType(
|
o.importType(
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileStylesheetMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
|
||||||
import {CompilerConfig} from '../config';
|
import {CompilerConfig} from '../config';
|
||||||
|
@ -44,7 +44,7 @@ export class JitCompiler implements Compiler {
|
||||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||||
private _compilerConfig: CompilerConfig) {}
|
private _compilerConfig: CompilerConfig, private _console: Console) {}
|
||||||
|
|
||||||
get injector(): Injector { return this._injector; }
|
get injector(): Injector { return this._injector; }
|
||||||
|
|
||||||
|
@ -66,6 +66,8 @@ export class JitCompiler implements Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
getNgContentSelectors(component: Type<any>): string[] {
|
getNgContentSelectors(component: Type<any>): string[] {
|
||||||
|
this._console.warn(
|
||||||
|
'Compiler.getNgContentSelectors is deprecated. Use ComponentFactory.ngContentSelectors instead!');
|
||||||
const template = this._compiledTemplateCache.get(component);
|
const template = this._compiledTemplateCache.get(component);
|
||||||
if (!template) {
|
if (!template) {
|
||||||
throw new Error(`The component ${stringify(component)} is not yet compiled!`);
|
throw new Error(`The component ${stringify(component)} is not yet compiled!`);
|
||||||
|
|
|
@ -142,7 +142,26 @@ export class CompileMetadataResolver {
|
||||||
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
|
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
|
||||||
} else {
|
} else {
|
||||||
const hostView = this.getHostComponentViewClass(dirType);
|
const hostView = this.getHostComponentViewClass(dirType);
|
||||||
return createComponentFactory(selector, dirType, <any>hostView);
|
// Note: inputs / outputs / ngContentSelectors will be filled later once the template is
|
||||||
|
// loaded.
|
||||||
|
return createComponentFactory(selector, dirType, <any>hostView, {}, {}, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initComponentFactory(
|
||||||
|
factory: StaticSymbol|ComponentFactory<any>, 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,
|
componentFactory: metadata.componentFactory,
|
||||||
template: templateMetadata
|
template: templateMetadata
|
||||||
});
|
});
|
||||||
|
if (templateMetadata) {
|
||||||
|
this.initComponentFactory(
|
||||||
|
metadata.componentFactory, metadata.inputs, metadata.outputs,
|
||||||
|
templateMetadata.ngContentSelectors);
|
||||||
|
}
|
||||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||||
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
this._summaryCache.set(directiveType, normalizedDirMeta.toSummary());
|
||||||
return normalizedDirMeta;
|
return normalizedDirMeta;
|
||||||
|
|
|
@ -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`);
|
`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: '<ng-content></ng-content><ng-content select="child"></ng-content>'
|
||||||
|
})
|
||||||
|
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)', () => {
|
describe('compiler (bundled Angular)', () => {
|
||||||
|
|
|
@ -73,6 +73,8 @@ export class Compiler {
|
||||||
* the template of the given component.
|
* the template of the given component.
|
||||||
* This is used by the `upgrade` library to compile the appropriate transclude content
|
* This is used by the `upgrade` library to compile the appropriate transclude content
|
||||||
* in the AngularJS wrapper component.
|
* in the AngularJS wrapper component.
|
||||||
|
*
|
||||||
|
* @deprecated since v4. Use ComponentFactory.ngContentSelectors instead.
|
||||||
*/
|
*/
|
||||||
getNgContentSelectors(component: Type<any>): string[] { throw _throwError(); }
|
getNgContentSelectors(component: Type<any>): string[] { throw _throwError(); }
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,18 @@ export abstract class ComponentRef<C> {
|
||||||
export abstract class ComponentFactory<C> {
|
export abstract class ComponentFactory<C> {
|
||||||
abstract get selector(): string;
|
abstract get selector(): string;
|
||||||
abstract get componentType(): Type<any>;
|
abstract get componentType(): Type<any>;
|
||||||
|
/**
|
||||||
|
* selector for all <ng-content> 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.
|
* Creates a new component.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -65,6 +65,9 @@ export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> {
|
||||||
|
|
||||||
get selector() { return this.factory.selector; }
|
get selector() { return this.factory.selector; }
|
||||||
get componentType() { return this.factory.componentType; }
|
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(
|
create(
|
||||||
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
||||||
|
|
|
@ -26,9 +26,21 @@ import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachVi
|
||||||
const EMPTY_CONTEXT = new Object();
|
const EMPTY_CONTEXT = new Object();
|
||||||
|
|
||||||
export function createComponentFactory(
|
export function createComponentFactory(
|
||||||
selector: string, componentType: Type<any>,
|
selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory,
|
||||||
viewDefFactory: ViewDefinitionFactory): ComponentFactory<any> {
|
inputs: {[propName: string]: string}, outputs: {[propName: string]: string},
|
||||||
return new ComponentFactory_(selector, componentType, viewDefFactory);
|
ngContentSelectors: string[]): ComponentFactory<any> {
|
||||||
|
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<any>):
|
export function getComponentViewDefinitionFactory(componentFactory: ComponentFactory<any>):
|
||||||
|
@ -44,7 +56,10 @@ class ComponentFactory_ extends ComponentFactory<any> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public selector: string, public componentType: Type<any>,
|
public selector: string, public componentType: Type<any>,
|
||||||
viewDefFactory: ViewDefinitionFactory) {
|
viewDefFactory: ViewDefinitionFactory,
|
||||||
|
public inputs: {propName: string, templateName: string}[],
|
||||||
|
public outputs: {propName: string, templateName: string}[],
|
||||||
|
public ngContentSelectors: string[]) {
|
||||||
super();
|
super();
|
||||||
this.viewDefFactory = viewDefFactory;
|
this.viewDefFactory = viewDefFactory;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue