feat(ivy): generate ngInjectorDef for @NgModule in AOT mode (#24632)
This change generates ngInjectorDef as well as ngModuleDef for @NgModule annotated types, reflecting the dual nature of @NgModules as both compilation scopes and as DI configuration containers. This required implementing ngInjectorDef compilation in @angular/compiler as well as allowing for multiple generated definitions for a single decorator in the core of ngtsc. PR Close #24632
This commit is contained in:
		
							parent
							
								
									166d90d2a9
								
							
						
					
					
						commit
						ae9418c7de
					
				| @ -116,7 +116,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe | |||||||
| 
 | 
 | ||||||
|     const res = compileComponentFromMetadata(analysis, pool, makeBindingParser()); |     const res = compileComponentFromMetadata(analysis, pool, makeBindingParser()); | ||||||
|     return { |     return { | ||||||
|       field: 'ngComponentDef', |       name: 'ngComponentDef', | ||||||
|       initializer: res.expression, |       initializer: res.expression, | ||||||
|       statements: pool.statements, |       statements: pool.statements, | ||||||
|       type: res.type, |       type: res.type, | ||||||
|  | |||||||
| @ -44,7 +44,7 @@ export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMe | |||||||
|     const pool = new ConstantPool(); |     const pool = new ConstantPool(); | ||||||
|     const res = compileDirectiveFromMetadata(analysis, pool, makeBindingParser()); |     const res = compileDirectiveFromMetadata(analysis, pool, makeBindingParser()); | ||||||
|     return { |     return { | ||||||
|       field: 'ngDirectiveDef', |       name: 'ngDirectiveDef', | ||||||
|       initializer: res.expression, |       initializer: res.expression, | ||||||
|       statements: pool.statements, |       statements: pool.statements, | ||||||
|       type: res.type, |       type: res.type, | ||||||
|  | |||||||
| @ -35,7 +35,7 @@ export class InjectableDecoratorHandler implements DecoratorHandler<R3Injectable | |||||||
|   compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): CompileResult { |   compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): CompileResult { | ||||||
|     const res = compileIvyInjectable(analysis); |     const res = compileIvyInjectable(analysis); | ||||||
|     return { |     return { | ||||||
|       field: 'ngInjectableDef', |       name: 'ngInjectableDef', | ||||||
|       initializer: res.expression, |       initializer: res.expression, | ||||||
|       statements: [], |       statements: [], | ||||||
|       type: res.type, |       type: res.type, | ||||||
|  | |||||||
| @ -6,29 +6,36 @@ | |||||||
|  * found in the LICENSE file at https://angular.io/license
 |  * found in the LICENSE file at https://angular.io/license
 | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import {ConstantPool, Expression, R3DirectiveMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler'; | import {ConstantPool, Expression, LiteralArrayExpr, R3DirectiveMetadata, R3InjectorMetadata, R3NgModuleMetadata, WrappedNodeExpr, compileInjector, compileNgModule, makeBindingParser, parseTemplate} from '@angular/compiler'; | ||||||
| import * as ts from 'typescript'; | import * as ts from 'typescript'; | ||||||
| 
 | 
 | ||||||
| import {Decorator} from '../../host'; | import {Decorator, ReflectionHost} from '../../host'; | ||||||
| import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata'; | import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata'; | ||||||
| import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; | import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform'; | ||||||
| 
 | 
 | ||||||
| import {SelectorScopeRegistry} from './selector_scope'; | import {SelectorScopeRegistry} from './selector_scope'; | ||||||
| import {isAngularCore, referenceToExpression} from './util'; | import {getConstructorDependencies, isAngularCore, referenceToExpression} from './util'; | ||||||
|  | 
 | ||||||
|  | export interface NgModuleAnalysis { | ||||||
|  |   ngModuleDef: R3NgModuleMetadata; | ||||||
|  |   ngInjectorDef: R3InjectorMetadata; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Compiles @NgModule annotations to ngModuleDef fields. |  * Compiles @NgModule annotations to ngModuleDef fields. | ||||||
|  * |  * | ||||||
|  * TODO(alxhub): handle injector side of things as well. |  * TODO(alxhub): handle injector side of things as well. | ||||||
|  */ |  */ | ||||||
| export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMetadata> { | export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis> { | ||||||
|   constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {} |   constructor( | ||||||
|  |       private checker: ts.TypeChecker, private reflector: ReflectionHost, | ||||||
|  |       private scopeRegistry: SelectorScopeRegistry) {} | ||||||
| 
 | 
 | ||||||
|   detect(decorators: Decorator[]): Decorator|undefined { |   detect(decorators: Decorator[]): Decorator|undefined { | ||||||
|     return decorators.find(decorator => decorator.name === 'NgModule' && isAngularCore(decorator)); |     return decorators.find(decorator => decorator.name === 'NgModule' && isAngularCore(decorator)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3NgModuleMetadata> { |   analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> { | ||||||
|     if (decorator.args === null || decorator.args.length !== 1) { |     if (decorator.args === null || decorator.args.length !== 1) { | ||||||
|       throw new Error(`Incorrect number of arguments to @NgModule decorator`); |       throw new Error(`Incorrect number of arguments to @NgModule decorator`); | ||||||
|     } |     } | ||||||
| @ -66,26 +73,51 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMeta | |||||||
| 
 | 
 | ||||||
|     const context = node.getSourceFile(); |     const context = node.getSourceFile(); | ||||||
| 
 | 
 | ||||||
|  |     const ngModuleDef: R3NgModuleMetadata = { | ||||||
|  |       type: new WrappedNodeExpr(node.name !), | ||||||
|  |       bootstrap: [], | ||||||
|  |       declarations: declarations.map(decl => referenceToExpression(decl, context)), | ||||||
|  |       exports: exports.map(exp => referenceToExpression(exp, context)), | ||||||
|  |       imports: imports.map(imp => referenceToExpression(imp, context)), | ||||||
|  |       emitInline: false, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const providers: Expression = ngModule.has('providers') ? | ||||||
|  |         new WrappedNodeExpr(ngModule.get('providers') !) : | ||||||
|  |         new LiteralArrayExpr([]); | ||||||
|  | 
 | ||||||
|  |     const ngInjectorDef: R3InjectorMetadata = { | ||||||
|  |       name: node.name !.text, | ||||||
|  |       type: new WrappedNodeExpr(node.name !), | ||||||
|  |       deps: getConstructorDependencies(node, this.reflector), providers, | ||||||
|  |       imports: new LiteralArrayExpr( | ||||||
|  |           [...imports, ...exports].map(imp => referenceToExpression(imp, context))), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     return { |     return { | ||||||
|       analysis: { |       analysis: { | ||||||
|         type: new WrappedNodeExpr(node.name !), |           ngModuleDef, ngInjectorDef, | ||||||
|         bootstrap: [], |  | ||||||
|         declarations: declarations.map(decl => referenceToExpression(decl, context)), |  | ||||||
|         exports: exports.map(exp => referenceToExpression(exp, context)), |  | ||||||
|         imports: imports.map(imp => referenceToExpression(imp, context)), |  | ||||||
|         emitInline: false, |  | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   compile(node: ts.ClassDeclaration, analysis: R3NgModuleMetadata): CompileResult { |   compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] { | ||||||
|     const res = compileNgModule(analysis); |     const ngInjectorDef = compileInjector(analysis.ngInjectorDef); | ||||||
|     return { |     const ngModuleDef = compileNgModule(analysis.ngModuleDef); | ||||||
|       field: 'ngModuleDef', |     return [ | ||||||
|       initializer: res.expression, |       { | ||||||
|       statements: [], |         name: 'ngModuleDef', | ||||||
|       type: res.type, |         initializer: ngModuleDef.expression, | ||||||
|     }; |         statements: [], | ||||||
|  |         type: ngModuleDef.type, | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: 'ngInjectorDef', | ||||||
|  |         initializer: ngInjectorDef.expression, | ||||||
|  |         statements: [], | ||||||
|  |         type: ngInjectorDef.type, | ||||||
|  |       }, | ||||||
|  |     ]; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ export class NgtscProgram implements api.Program { | |||||||
|       new ComponentDecoratorHandler(checker, reflector, scopeRegistry), |       new ComponentDecoratorHandler(checker, reflector, scopeRegistry), | ||||||
|       new DirectiveDecoratorHandler(checker, reflector, scopeRegistry), |       new DirectiveDecoratorHandler(checker, reflector, scopeRegistry), | ||||||
|       new InjectableDecoratorHandler(reflector), |       new InjectableDecoratorHandler(reflector), | ||||||
|       new NgModuleDecoratorHandler(checker, scopeRegistry), |       new NgModuleDecoratorHandler(checker, reflector, scopeRegistry), | ||||||
|     ]; |     ]; | ||||||
|     const compilation = new IvyCompilation(handlers, checker, reflector); |     const compilation = new IvyCompilation(handlers, checker, reflector); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ export interface DecoratorHandler<A> { | |||||||
|    * Generate a description of the field which should be added to the class, including any |    * Generate a description of the field which should be added to the class, including any | ||||||
|    * initialization code to be generated. |    * initialization code to be generated. | ||||||
|    */ |    */ | ||||||
|   compile(node: ts.Declaration, analysis: A): CompileResult; |   compile(node: ts.Declaration, analysis: A): CompileResult|CompileResult[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -55,7 +55,7 @@ export interface AnalysisOutput<A> { | |||||||
|  * and a type for the .d.ts file. |  * and a type for the .d.ts file. | ||||||
|  */ |  */ | ||||||
| export interface CompileResult { | export interface CompileResult { | ||||||
|   field: string; |   name: string; | ||||||
|   initializer: Expression; |   initializer: Expression; | ||||||
|   statements: Statement[]; |   statements: Statement[]; | ||||||
|   type: Type; |   type: Type; | ||||||
|  | |||||||
| @ -107,7 +107,7 @@ export class IvyCompilation { | |||||||
|    * Perform a compilation operation on the given class declaration and return instructions to an |    * Perform a compilation operation on the given class declaration and return instructions to an | ||||||
|    * AST transformer if any are available. |    * AST transformer if any are available. | ||||||
|    */ |    */ | ||||||
|   compileIvyFieldFor(node: ts.Declaration): CompileResult|undefined { |   compileIvyFieldFor(node: ts.Declaration): CompileResult[]|undefined { | ||||||
|     // Look to see whether the original node was analyzed. If not, there's nothing to do.
 |     // Look to see whether the original node was analyzed. If not, there's nothing to do.
 | ||||||
|     const original = ts.getOriginalNode(node) as ts.Declaration; |     const original = ts.getOriginalNode(node) as ts.Declaration; | ||||||
|     if (!this.analysis.has(original)) { |     if (!this.analysis.has(original)) { | ||||||
| @ -116,7 +116,10 @@ export class IvyCompilation { | |||||||
|     const op = this.analysis.get(original) !; |     const op = this.analysis.get(original) !; | ||||||
| 
 | 
 | ||||||
|     // Run the actual compilation, which generates an Expression for the Ivy field.
 |     // Run the actual compilation, which generates an Expression for the Ivy field.
 | ||||||
|     const res = op.adapter.compile(node, op.analysis); |     let res: CompileResult|CompileResult[] = op.adapter.compile(node, op.analysis); | ||||||
|  |     if (!Array.isArray(res)) { | ||||||
|  |       res = [res]; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Look up the .d.ts transformer for the input file and record that a field was generated,
 |     // Look up the .d.ts transformer for the input file and record that a field was generated,
 | ||||||
|     // which will allow the .d.ts to be transformed later.
 |     // which will allow the .d.ts to be transformed later.
 | ||||||
|  | |||||||
| @ -17,13 +17,13 @@ import {ImportManager, translateType} from './translator'; | |||||||
|  * Processes .d.ts file text and adds static field declarations, with types. |  * Processes .d.ts file text and adds static field declarations, with types. | ||||||
|  */ |  */ | ||||||
| export class DtsFileTransformer { | export class DtsFileTransformer { | ||||||
|   private ivyFields = new Map<string, CompileResult>(); |   private ivyFields = new Map<string, CompileResult[]>(); | ||||||
|   private imports = new ImportManager(); |   private imports = new ImportManager(); | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Track that a static field was added to the code for a class. |    * Track that a static field was added to the code for a class. | ||||||
|    */ |    */ | ||||||
|   recordStaticField(name: string, decl: CompileResult): void { this.ivyFields.set(name, decl); } |   recordStaticField(name: string, decls: CompileResult[]): void { this.ivyFields.set(name, decls); } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Process the .d.ts text for a file and add any declarations which were recorded. |    * Process the .d.ts text for a file and add any declarations which were recorded. | ||||||
| @ -36,11 +36,18 @@ export class DtsFileTransformer { | |||||||
|       const stmt = dtsFile.statements[i]; |       const stmt = dtsFile.statements[i]; | ||||||
|       if (ts.isClassDeclaration(stmt) && stmt.name !== undefined && |       if (ts.isClassDeclaration(stmt) && stmt.name !== undefined && | ||||||
|           this.ivyFields.has(stmt.name.text)) { |           this.ivyFields.has(stmt.name.text)) { | ||||||
|         const desc = this.ivyFields.get(stmt.name.text) !; |         const decls = this.ivyFields.get(stmt.name.text) !; | ||||||
|         const before = dts.substring(0, stmt.end - 1); |         const before = dts.substring(0, stmt.end - 1); | ||||||
|         const after = dts.substring(stmt.end - 1); |         const after = dts.substring(stmt.end - 1); | ||||||
|         const type = translateType(desc.type, this.imports); | 
 | ||||||
|         dts = before + `    static ${desc.field}: ${type};\n` + after; |         dts = before + | ||||||
|  |             decls | ||||||
|  |                 .map(decl => { | ||||||
|  |                   const type = translateType(decl.type, this.imports); | ||||||
|  |                   return `    static ${decl.name}: ${type};\n`; | ||||||
|  |                 }) | ||||||
|  |                 .join('') + | ||||||
|  |             after; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ import * as ts from 'typescript'; | |||||||
| 
 | 
 | ||||||
| import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor'; | import {VisitListEntryResult, Visitor, visit} from '../../util/src/visitor'; | ||||||
| 
 | 
 | ||||||
|  | import {CompileResult} from './api'; | ||||||
| import {IvyCompilation} from './compilation'; | import {IvyCompilation} from './compilation'; | ||||||
| import {ImportManager, translateExpression, translateStatement} from './translator'; | import {ImportManager, translateExpression, translateStatement} from './translator'; | ||||||
| 
 | 
 | ||||||
| @ -33,14 +34,26 @@ class IvyVisitor extends Visitor { | |||||||
|     // Determine if this class has an Ivy field that needs to be added, and compile the field
 |     // Determine if this class has an Ivy field that needs to be added, and compile the field
 | ||||||
|     // to an expression if so.
 |     // to an expression if so.
 | ||||||
|     const res = this.compilation.compileIvyFieldFor(node); |     const res = this.compilation.compileIvyFieldFor(node); | ||||||
|     if (res !== undefined) { |  | ||||||
|       // There is a field to add. Translate the initializer for the field into TS nodes.
 |  | ||||||
|       const exprNode = translateExpression(res.initializer, this.importManager); |  | ||||||
| 
 | 
 | ||||||
|       // Create a static property declaration for the new field.
 |     if (res !== undefined) { | ||||||
|       const property = ts.createProperty( |       // There is at least one field to add.
 | ||||||
|           undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], res.field, undefined, undefined, |       const statements: ts.Statement[] = []; | ||||||
|           exprNode); |       const members = [...node.members]; | ||||||
|  | 
 | ||||||
|  |       res.forEach(field => { | ||||||
|  |         // Translate the initializer for the field into TS nodes.
 | ||||||
|  |         const exprNode = translateExpression(field.initializer, this.importManager); | ||||||
|  | 
 | ||||||
|  |         // Create a static property declaration for the new field.
 | ||||||
|  |         const property = ts.createProperty( | ||||||
|  |             undefined, [ts.createToken(ts.SyntaxKind.StaticKeyword)], field.name, undefined, | ||||||
|  |             undefined, exprNode); | ||||||
|  | 
 | ||||||
|  |         field.statements.map(stmt => translateStatement(stmt, this.importManager)) | ||||||
|  |             .forEach(stmt => statements.push(stmt)); | ||||||
|  | 
 | ||||||
|  |         members.push(property); | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|       // Replace the class declaration with an updated version.
 |       // Replace the class declaration with an updated version.
 | ||||||
|       node = ts.updateClassDeclaration( |       node = ts.updateClassDeclaration( | ||||||
| @ -48,9 +61,7 @@ class IvyVisitor extends Visitor { | |||||||
|           // Remove the decorator which triggered this compilation, leaving the others alone.
 |           // Remove the decorator which triggered this compilation, leaving the others alone.
 | ||||||
|           maybeFilterDecorator( |           maybeFilterDecorator( | ||||||
|               node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator), |               node.decorators, this.compilation.ivyDecoratorFor(node) !.node as ts.Decorator), | ||||||
|           node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], |           node.modifiers, node.name, node.typeParameters, node.heritageClauses || [], members); | ||||||
|           [...node.members, property]); |  | ||||||
|       const statements = res.statements.map(stmt => translateStatement(stmt, this.importManager)); |  | ||||||
|       return {node, before: statements}; |       return {node, before: statements}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -180,4 +180,46 @@ describe('ngtsc behavioral tests', () => { | |||||||
|         .toContain('static ngModuleDef: i0.NgModuleDef<TestModule, [TestCmp], [], []>'); |         .toContain('static ngModuleDef: i0.NgModuleDef<TestModule, [TestCmp], [], []>'); | ||||||
|     expect(dtsContents).not.toContain('__decorate'); |     expect(dtsContents).not.toContain('__decorate'); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   it('should compile NgModules with services without errors', () => { | ||||||
|  |     writeConfig(); | ||||||
|  |     write('test.ts', ` | ||||||
|  |         import {Component, NgModule} from '@angular/core'; | ||||||
|  | 
 | ||||||
|  |         export class Token {} | ||||||
|  | 
 | ||||||
|  |         @NgModule({}) | ||||||
|  |         export class OtherModule {} | ||||||
|  | 
 | ||||||
|  |         @Component({ | ||||||
|  |           selector: 'test-cmp', | ||||||
|  |           template: 'this is a test', | ||||||
|  |         }) | ||||||
|  |         export class TestCmp {} | ||||||
|  | 
 | ||||||
|  |         @NgModule({ | ||||||
|  |           declarations: [TestCmp], | ||||||
|  |           providers: [{provide: Token, useValue: 'test'}], | ||||||
|  |           imports: [OtherModule], | ||||||
|  |         }) | ||||||
|  |         export class TestModule {} | ||||||
|  |     `);
 | ||||||
|  | 
 | ||||||
|  |     const exitCode = main(['-p', basePath], errorSpy); | ||||||
|  |     expect(errorSpy).not.toHaveBeenCalled(); | ||||||
|  |     expect(exitCode).toBe(0); | ||||||
|  | 
 | ||||||
|  |     const jsContents = getContents('test.js'); | ||||||
|  |     expect(jsContents).toContain('i0.ɵdefineNgModule({ type: TestModule,'); | ||||||
|  |     expect(jsContents) | ||||||
|  |         .toContain( | ||||||
|  |             `TestModule.ngInjectorDef = i0.defineInjector({ factory: ` + | ||||||
|  |             `function TestModule_Factory() { return new TestModule(); }, providers: [{ provide: ` + | ||||||
|  |             `Token, useValue: 'test' }], imports: [OtherModule] });`); | ||||||
|  | 
 | ||||||
|  |     const dtsContents = getContents('test.d.ts'); | ||||||
|  |     expect(dtsContents) | ||||||
|  |         .toContain('static ngModuleDef: i0.NgModuleDef<TestModule, [TestCmp], [OtherModule], []>'); | ||||||
|  |     expect(dtsContents).toContain('static ngInjectorDef: i0.InjectorDef'); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -83,7 +83,7 @@ export * from './injectable_compiler_2'; | |||||||
| export * from './render3/view/api'; | export * from './render3/view/api'; | ||||||
| export {jitExpression} from './render3/r3_jit'; | export {jitExpression} from './render3/r3_jit'; | ||||||
| export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; | export {R3DependencyMetadata, R3FactoryMetadata, R3ResolvedDependencyType} from './render3/r3_factory'; | ||||||
| export {compileNgModule, R3NgModuleMetadata} from './render3/r3_module_compiler'; | export {compileInjector, compileNgModule, R3InjectorMetadata, R3NgModuleMetadata} from './render3/r3_module_compiler'; | ||||||
| export {makeBindingParser, parseTemplate} from './render3/view/template'; | export {makeBindingParser, parseTemplate} from './render3/view/template'; | ||||||
| export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler'; | export {compileComponentFromMetadata, compileDirectiveFromMetadata, parseHostBindings} from './render3/view/compiler'; | ||||||
| // This file only reexports content of the `src` folder. Keep it that way.
 | // This file only reexports content of the `src` folder. Keep it that way.
 | ||||||
| @ -114,6 +114,11 @@ export class Identifiers { | |||||||
|     moduleName: CORE, |     moduleName: CORE, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   static InjectorDef: o.ExternalReference = { | ||||||
|  |     name: 'InjectorDef', | ||||||
|  |     moduleName: CORE, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   static defineInjector: o.ExternalReference = { |   static defineInjector: o.ExternalReference = { | ||||||
|     name: 'defineInjector', |     name: 'defineInjector', | ||||||
|     moduleName: CORE, |     moduleName: CORE, | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ import {mapLiteral} from '../output/map_util'; | |||||||
| import * as o from '../output/output_ast'; | import * as o from '../output/output_ast'; | ||||||
| import {OutputContext} from '../util'; | import {OutputContext} from '../util'; | ||||||
| 
 | 
 | ||||||
|  | import {R3DependencyMetadata, compileFactoryFunction} from './r3_factory'; | ||||||
| import {Identifiers as R3} from './r3_identifiers'; | import {Identifiers as R3} from './r3_identifiers'; | ||||||
| import {convertMetaToOutput, mapToMapExpression} from './util'; | import {convertMetaToOutput, mapToMapExpression} from './util'; | ||||||
| 
 | 
 | ||||||
| @ -81,6 +82,35 @@ export function compileNgModule(meta: R3NgModuleMetadata): R3NgModuleDef { | |||||||
|   return {expression, type, additionalStatements}; |   return {expression, type, additionalStatements}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export interface R3InjectorDef { | ||||||
|  |   expression: o.Expression; | ||||||
|  |   type: o.Type; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface R3InjectorMetadata { | ||||||
|  |   name: string; | ||||||
|  |   type: o.Expression; | ||||||
|  |   deps: R3DependencyMetadata[]; | ||||||
|  |   providers: o.Expression; | ||||||
|  |   imports: o.Expression; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function compileInjector(meta: R3InjectorMetadata): R3InjectorDef { | ||||||
|  |   const expression = o.importExpr(R3.defineInjector).callFn([mapToMapExpression({ | ||||||
|  |     factory: compileFactoryFunction({ | ||||||
|  |       name: meta.name, | ||||||
|  |       fnOrClass: meta.type, | ||||||
|  |       deps: meta.deps, | ||||||
|  |       useNew: true, | ||||||
|  |       injectFn: R3.inject, | ||||||
|  |     }), | ||||||
|  |     providers: meta.providers, | ||||||
|  |     imports: meta.imports, | ||||||
|  |   })]); | ||||||
|  |   const type = new o.ExpressionType(o.importExpr(R3.InjectorDef)); | ||||||
|  |   return {expression, type}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations.
 | // TODO(alxhub): integrate this with `compileNgModule`. Currently the two are separate operations.
 | ||||||
| export function compileNgModuleFromRender2( | export function compileNgModuleFromRender2( | ||||||
|     ctx: OutputContext, ngModule: CompileShallowModuleMetadata, |     ctx: OutputContext, ngModule: CompileShallowModuleMetadata, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user