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); | ||||
|     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 { | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
| @ -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<any>): 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!`); | ||||
|  | ||||
| @ -142,7 +142,26 @@ export class CompileMetadataResolver { | ||||
|           ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType)); | ||||
|     } else { | ||||
|       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, | ||||
|         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; | ||||
|  | ||||
| @ -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: '<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)', () => { | ||||
|  | ||||
| @ -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<any>): string[] { throw _throwError(); } | ||||
| 
 | ||||
|  | ||||
| @ -70,6 +70,18 @@ export abstract class ComponentRef<C> { | ||||
| export abstract class ComponentFactory<C> { | ||||
|   abstract get selector(): string; | ||||
|   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. | ||||
|    */ | ||||
|  | ||||
| @ -65,6 +65,9 @@ export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> { | ||||
| 
 | ||||
|   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, | ||||
|  | ||||
| @ -26,9 +26,21 @@ import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachVi | ||||
| const EMPTY_CONTEXT = new Object(); | ||||
| 
 | ||||
| export function createComponentFactory( | ||||
|     selector: string, componentType: Type<any>, | ||||
|     viewDefFactory: ViewDefinitionFactory): ComponentFactory<any> { | ||||
|   return new ComponentFactory_(selector, componentType, viewDefFactory); | ||||
|     selector: string, componentType: Type<any>, viewDefFactory: ViewDefinitionFactory, | ||||
|     inputs: {[propName: string]: string}, outputs: {[propName: string]: string}, | ||||
|     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>): | ||||
| @ -44,7 +56,10 @@ class ComponentFactory_ extends ComponentFactory<any> { | ||||
| 
 | ||||
|   constructor( | ||||
|       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(); | ||||
|     this.viewDefFactory = viewDefFactory; | ||||
|   } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user