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:
Alex Rickabaugh
2018-06-18 16:28:02 -07:00
committed by Jason Aden
parent 166d90d2a9
commit ae9418c7de
13 changed files with 174 additions and 44 deletions

View File

@ -116,7 +116,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
const res = compileComponentFromMetadata(analysis, pool, makeBindingParser());
return {
field: 'ngComponentDef',
name: 'ngComponentDef',
initializer: res.expression,
statements: pool.statements,
type: res.type,

View File

@ -44,7 +44,7 @@ export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMe
const pool = new ConstantPool();
const res = compileDirectiveFromMetadata(analysis, pool, makeBindingParser());
return {
field: 'ngDirectiveDef',
name: 'ngDirectiveDef',
initializer: res.expression,
statements: pool.statements,
type: res.type,

View File

@ -35,7 +35,7 @@ export class InjectableDecoratorHandler implements DecoratorHandler<R3Injectable
compile(node: ts.ClassDeclaration, analysis: R3InjectableMetadata): CompileResult {
const res = compileIvyInjectable(analysis);
return {
field: 'ngInjectableDef',
name: 'ngInjectableDef',
initializer: res.expression,
statements: [],
type: res.type,

View File

@ -6,29 +6,36 @@
* 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 {Decorator} from '../../host';
import {Decorator, ReflectionHost} from '../../host';
import {Reference, ResolvedValue, reflectObjectLiteral, staticallyResolve} from '../../metadata';
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
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.
*
* TODO(alxhub): handle injector side of things as well.
*/
export class NgModuleDecoratorHandler implements DecoratorHandler<R3NgModuleMetadata> {
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry) {}
detect(decorators: Decorator[]): Decorator|undefined {
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) {
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 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 {
analysis: {
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,
ngModuleDef, ngInjectorDef,
},
};
}
compile(node: ts.ClassDeclaration, analysis: R3NgModuleMetadata): CompileResult {
const res = compileNgModule(analysis);
return {
field: 'ngModuleDef',
initializer: res.expression,
statements: [],
type: res.type,
};
compile(node: ts.ClassDeclaration, analysis: NgModuleAnalysis): CompileResult[] {
const ngInjectorDef = compileInjector(analysis.ngInjectorDef);
const ngModuleDef = compileNgModule(analysis.ngModuleDef);
return [
{
name: 'ngModuleDef',
initializer: ngModuleDef.expression,
statements: [],
type: ngModuleDef.type,
},
{
name: 'ngInjectorDef',
initializer: ngInjectorDef.expression,
statements: [],
type: ngInjectorDef.type,
},
];
}
}