refactor: use view engine also for NgModuleFactorys (#16658)

* refactor(core): provide error message in stack for reflective DI

Fixes #16355

* fix(compiler): make AOT work with `noUnusedParameters`

Fixes #15532

* refactor: use view engine also for `NgModuleFactory`s

This is a prerequisite for being able to mock providers
in AOTed code later on.
This commit is contained in:
Tobias Bosch
2017-05-11 10:26:02 -07:00
committed by Jason Aden
parent b9521b568f
commit ce1d7c4a6e
25 changed files with 800 additions and 604 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, NgModuleRef, QueryList, Renderer, SecurityContext, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation, ɵCodegenComponentFactoryResolver, ɵEMPTY_ARRAY, ɵEMPTY_MAP, ɵNgModuleInjector, ɵand, ɵccf, ɵcrt, ɵdid, ɵeld, ɵinlineInterpolate, ɵinterpolate, ɵncd, ɵnov, ɵpad, ɵpid, ɵpod, ɵppd, ɵprd, ɵqud, ɵreflector, ɵregisterModuleFactory, ɵted, ɵunv, ɵvid} from '@angular/core';
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, NgModuleRef, QueryList, Renderer, SecurityContext, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation, ɵCodegenComponentFactoryResolver, ɵEMPTY_ARRAY, ɵEMPTY_MAP, ɵand, ɵccf, ɵcmf, ɵcrt, ɵdid, ɵeld, ɵinlineInterpolate, ɵinterpolate, ɵmod, ɵmpd, ɵncd, ɵnov, ɵpad, ɵpid, ɵpod, ɵppd, ɵprd, ɵqud, ɵreflector, ɵregisterModuleFactory, ɵted, ɵunv, ɵvid} from '@angular/core';
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
@ -48,10 +48,20 @@ export class Identifiers {
IdentifierSpec = {name: 'ComponentRef', moduleUrl: CORE, runtime: ComponentRef};
static NgModuleFactory:
IdentifierSpec = {name: 'NgModuleFactory', moduleUrl: CORE, runtime: NgModuleFactory};
static NgModuleInjector: IdentifierSpec = {
name: NgModuleInjector',
static createModuleFactory: IdentifierSpec = {
name: cmf',
moduleUrl: CORE,
runtime: ɵNgModuleInjector,
runtime: ɵcmf,
};
static moduleDef: IdentifierSpec = {
name: 'ɵmod',
moduleUrl: CORE,
runtime: ɵmod,
};
static moduleProviderDef: IdentifierSpec = {
name: 'ɵmpd',
moduleUrl: CORE,
runtime: ɵmpd,
};
static RegisterModuleFactoryFn: IdentifierSpec = {
name: 'ɵregisterModuleFactory',

View File

@ -6,67 +6,58 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵLifecycleHooks} from '@angular/core';
import {ɵNodeFlags as NodeFlags} from '@angular/core';
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
import {CompileNgModuleMetadata, CompileProviderMetadata, identifierName} from './compile_metadata';
import {Identifiers, createIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast';
import {convertValueToOutputAst} from './output/value_util';
import {ParseLocation, ParseSourceFile, ParseSourceSpan, typeSourceSpan} from './parse_util';
import {typeSourceSpan} from './parse_util';
import {NgModuleProviderAnalyzer} from './provider_analyzer';
import {ProviderAst} from './template_parser/template_ast';
/**
* This is currently not read, but will probably be used in the future.
* We keep it as we already pass it through all the rigth places...
*/
export class ComponentFactoryDependency {
constructor(public compType: any) {}
}
import {componentFactoryResolverProviderDef, depDef, providerDef} from './view_compiler/provider_compiler';
export class NgModuleCompileResult {
constructor(
public statements: o.Statement[], public ngModuleFactoryVar: string,
public dependencies: ComponentFactoryDependency[]) {}
constructor(public statements: o.Statement[], public ngModuleFactoryVar: string) {}
}
const LOG_VAR = o.variable('_l');
@CompilerInjectable()
export class NgModuleCompiler {
compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]):
NgModuleCompileResult {
const sourceSpan = typeSourceSpan('NgModule', ngModuleMeta.type);
const deps: ComponentFactoryDependency[] = [];
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
const entryComponentFactories =
ngModuleMeta.transitiveModule.entryComponents.map((entryComponent) => {
if (ngModuleMeta.bootstrapComponents.some(
(id) => id.reference === entryComponent.componentType)) {
bootstrapComponentFactories.push({reference: entryComponent.componentFactory});
}
deps.push(new ComponentFactoryDependency(entryComponent.componentType));
return {reference: entryComponent.componentFactory};
});
const builder = new _InjectorBuilder(
ngModuleMeta, entryComponentFactories, bootstrapComponentFactories, sourceSpan);
const entryComponentFactories = ngModuleMeta.transitiveModule.entryComponents;
const bootstrapComponents = ngModuleMeta.bootstrapComponents;
const providerParser = new NgModuleProviderAnalyzer(ngModuleMeta, extraProviders, sourceSpan);
providerParser.parse().forEach((provider) => builder.addProvider(provider));
const injectorClass = builder.build();
const providerDefs =
[componentFactoryResolverProviderDef(NodeFlags.None, entryComponentFactories)]
.concat(providerParser.parse().map((provider) => providerDef(provider)))
.map(({providerExpr, depsExpr, flags, tokenExpr}) => {
return o.importExpr(createIdentifier(Identifiers.moduleProviderDef)).callFn([
o.literal(flags), tokenExpr, providerExpr, depsExpr
]);
});
const ngModuleDef =
o.importExpr(createIdentifier(Identifiers.moduleDef)).callFn([o.literalArr(providerDefs)]);
const ngModuleDefFactory = o.fn(
[new o.FnParam(LOG_VAR.name !)], [new o.ReturnStatement(ngModuleDef)], o.INFERRED_TYPE);
const ngModuleFactoryVar = `${identifierName(ngModuleMeta.type)}NgFactory`;
const ngModuleFactoryStmt =
o.variable(ngModuleFactoryVar)
.set(o.importExpr(createIdentifier(Identifiers.NgModuleFactory))
.instantiate(
[o.variable(injectorClass.name), o.importExpr(ngModuleMeta.type)],
o.importType(
createIdentifier(Identifiers.NgModuleFactory),
[o.importType(ngModuleMeta.type) !], [o.TypeModifier.Const])))
.toDeclStmt(null, [o.StmtModifier.Final]);
.set(o.importExpr(createIdentifier(Identifiers.createModuleFactory)).callFn([
o.importExpr(ngModuleMeta.type),
o.literalArr(bootstrapComponents.map(id => o.importExpr(id))), ngModuleDefFactory
]))
.toDeclStmt(
o.importType(
createIdentifier(Identifiers.NgModuleFactory),
[o.importType(ngModuleMeta.type) !], [o.TypeModifier.Const]),
[o.StmtModifier.Final]);
const stmts: o.Statement[] = [injectorClass, ngModuleFactoryStmt];
const stmts: o.Statement[] = [ngModuleFactoryStmt];
if (ngModuleMeta.id) {
const registerFactoryStmt =
o.importExpr(createIdentifier(Identifiers.RegisterModuleFactoryFn))
@ -75,185 +66,6 @@ export class NgModuleCompiler {
stmts.push(registerFactoryStmt);
}
return new NgModuleCompileResult(stmts, ngModuleFactoryVar, deps);
return new NgModuleCompileResult(stmts, ngModuleFactoryVar);
}
}
class _InjectorBuilder implements ClassBuilder {
fields: o.ClassField[] = [];
getters: o.ClassGetter[] = [];
methods: o.ClassMethod[] = [];
ctorStmts: o.Statement[] = [];
private _lazyProps = new Map<string, o.Expression>();
private _tokens: CompileTokenMetadata[] = [];
private _instances = new Map<any, o.Expression>();
private _createStmts: o.Statement[] = [];
private _destroyStmts: o.Statement[] = [];
constructor(
private _ngModuleMeta: CompileNgModuleMetadata,
private _entryComponentFactories: CompileIdentifierMetadata[],
private _bootstrapComponentFactories: CompileIdentifierMetadata[],
private _sourceSpan: ParseSourceSpan) {}
addProvider(resolvedProvider: ProviderAst) {
const providerValueExpressions =
resolvedProvider.providers.map((provider) => this._getProviderValue(provider));
const propName = `_${tokenName(resolvedProvider.token)}_${this._instances.size}`;
const instance = this._createProviderProperty(
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager);
if (resolvedProvider.lifecycleHooks.indexOf(ɵLifecycleHooks.OnDestroy) !== -1) {
let callNgOnDestroy: o.Expression = instance.callMethod('ngOnDestroy', []);
if (!resolvedProvider.eager) {
callNgOnDestroy = this._lazyProps.get(instance.name) !.and(callNgOnDestroy);
}
this._destroyStmts.push(callNgOnDestroy.toStmt());
}
this._tokens.push(resolvedProvider.token);
this._instances.set(tokenReference(resolvedProvider.token), instance);
}
build(): o.ClassStmt {
const getMethodStmts: o.Statement[] = this._tokens.map((token) => {
const providerExpr = this._instances.get(tokenReference(token)) !;
return new o.IfStmt(
InjectMethodVars.token.identical(createDiTokenExpression(token)),
[new o.ReturnStatement(providerExpr)]);
});
const methods = [
new o.ClassMethod(
'createInternal', [], this._createStmts.concat(new o.ReturnStatement(
this._instances.get(this._ngModuleMeta.type.reference) !)),
o.importType(this._ngModuleMeta.type)),
new o.ClassMethod(
'getInternal',
[
new o.FnParam(InjectMethodVars.token.name !, o.DYNAMIC_TYPE),
new o.FnParam(InjectMethodVars.notFoundResult.name !, o.DYNAMIC_TYPE)
],
getMethodStmts.concat([new o.ReturnStatement(InjectMethodVars.notFoundResult)]),
o.DYNAMIC_TYPE),
new o.ClassMethod('destroyInternal', [], this._destroyStmts),
];
const parentArgs = [
o.variable(InjectorProps.parent.name),
o.literalArr(
this._entryComponentFactories.map((componentFactory) => o.importExpr(componentFactory))),
o.literalArr(this._bootstrapComponentFactories.map(
(componentFactory) => o.importExpr(componentFactory)))
];
const injClassName = `${identifierName(this._ngModuleMeta.type)}Injector`;
return createClassStmt({
name: injClassName,
ctorParams: [new o.FnParam(
InjectorProps.parent.name, o.importType(createIdentifier(Identifiers.Injector)))],
parent: o.importExpr(
createIdentifier(Identifiers.NgModuleInjector),
[o.importType(this._ngModuleMeta.type) !]),
parentArgs: parentArgs,
builders: [{methods}, this]
});
}
private _getProviderValue(provider: CompileProviderMetadata): o.Expression {
let result: o.Expression;
if (provider.useExisting != null) {
result = this._getDependency({token: provider.useExisting});
} else if (provider.useFactory != null) {
const deps = provider.deps || provider.useFactory.diDeps;
const depsExpr = deps.map((dep) => this._getDependency(dep));
result = o.importExpr(provider.useFactory).callFn(depsExpr);
} else if (provider.useClass != null) {
const deps = provider.deps || provider.useClass.diDeps;
const depsExpr = deps.map((dep) => this._getDependency(dep));
result =
o.importExpr(provider.useClass).instantiate(depsExpr, o.importType(provider.useClass));
} else {
result = convertValueToOutputAst(provider.useValue);
}
return result;
}
private _createProviderProperty(
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[],
isMulti: boolean, isEager: boolean): o.ReadPropExpr {
let resolvedProviderValueExpr: o.Expression;
let type: o.Type;
if (isMulti) {
resolvedProviderValueExpr = o.literalArr(providerValueExpressions);
type = new o.ArrayType(o.DYNAMIC_TYPE);
} else {
resolvedProviderValueExpr = providerValueExpressions[0];
type = providerValueExpressions[0].type !;
}
if (!type) {
type = o.DYNAMIC_TYPE;
}
if (isEager) {
this.fields.push(new o.ClassField(propName, type));
this._createStmts.push(o.THIS_EXPR.prop(propName).set(resolvedProviderValueExpr).toStmt());
} else {
const internalFieldProp = o.THIS_EXPR.prop(`_${propName}`);
this.fields.push(new o.ClassField(internalFieldProp.name, type));
// Note: Equals is important for JS so that it also checks the undefined case!
const getterStmts = [
new o.IfStmt(
internalFieldProp.isBlank(),
[internalFieldProp.set(resolvedProviderValueExpr).toStmt()]),
new o.ReturnStatement(internalFieldProp)
];
this.getters.push(new o.ClassGetter(propName, getterStmts, type));
this._lazyProps.set(propName, internalFieldProp);
}
return o.THIS_EXPR.prop(propName);
}
private _getDependency(dep: CompileDiDependencyMetadata): o.Expression {
let result: o.Expression = null !;
if (dep.isValue) {
result = o.literal(dep.value);
}
if (!dep.isSkipSelf) {
if (dep.token) {
if (tokenReference(dep.token) === resolveIdentifier(Identifiers.Injector)) {
result = o.THIS_EXPR;
} else if (
tokenReference(dep.token) === resolveIdentifier(Identifiers.ComponentFactoryResolver)) {
result = o.THIS_EXPR.prop('componentFactoryResolver');
}
}
if (!result) {
result = this._instances.get(tokenReference(dep.token !)) !;
}
}
if (!result) {
const args = [createDiTokenExpression(dep.token !)];
if (dep.isOptional) {
args.push(o.NULL_EXPR);
}
result = InjectorProps.parent.callMethod('get', args);
}
return result;
}
}
function createDiTokenExpression(token: CompileTokenMetadata): o.Expression {
if (token.value != null) {
return o.literal(token.value);
} else {
return o.importExpr(token.identifier !);
}
}
class InjectorProps {
static parent = o.THIS_EXPR.prop('parent');
}
class InjectMethodVars {
static token = o.variable('token');
static notFoundResult = o.variable('notFoundResult');
}

View File

@ -96,7 +96,17 @@ export class ProviderElementContext {
}
get transformProviders(): ProviderAst[] {
return Array.from(this._transformedProviders.values());
// Note: Maps keep their insertion order.
const lazyProviders: ProviderAst[] = [];
const eagerProviders: ProviderAst[] = [];
this._transformedProviders.forEach(provider => {
if (provider.eager) {
eagerProviders.push(provider);
} else {
lazyProviders.push(provider);
}
});
return lazyProviders.concat(eagerProviders);
}
get transformedDirectiveAsts(): DirectiveAst[] {
@ -316,7 +326,17 @@ export class NgModuleProviderAnalyzer {
const errorString = this._errors.join('\n');
throw new Error(`Provider parse errors:\n${errorString}`);
}
return Array.from(this._transformedProviders.values());
// Note: Maps keep their insertion order.
const lazyProviders: ProviderAst[] = [];
const eagerProviders: ProviderAst[] = [];
this._transformedProviders.forEach(provider => {
if (provider.eager) {
eagerProviders.push(provider);
} else {
lazyProviders.push(provider);
}
});
return lazyProviders.concat(eagerProviders);
}
private _getOrCreateLocalProvider(token: CompileTokenMetadata, eager: boolean): ProviderAst|null {

View File

@ -0,0 +1,196 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ɵDepFlags as DepFlags, ɵLifecycleHooks as LifecycleHooks, ɵNodeFlags as NodeFlags} from '@angular/core';
import {CompileDiDependencyMetadata, CompileEntryComponentMetadata, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from '../identifiers';
import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util';
import {ProviderAst, ProviderAstType} from '../template_parser/template_ast';
export function providerDef(providerAst: ProviderAst): {
providerExpr: o.Expression,
flags: NodeFlags,
depsExpr: o.Expression,
tokenExpr: o.Expression
} {
let flags = NodeFlags.None;
if (!providerAst.eager) {
flags |= NodeFlags.LazyProvider;
}
if (providerAst.providerType === ProviderAstType.PrivateService) {
flags |= NodeFlags.PrivateProvider;
}
providerAst.lifecycleHooks.forEach((lifecycleHook) => {
// for regular providers, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy ||
providerAst.providerType === ProviderAstType.Directive ||
providerAst.providerType === ProviderAstType.Component) {
flags |= lifecycleHookToNodeFlag(lifecycleHook);
}
});
const {providerExpr, flags: providerFlags, depsExpr} = providerAst.multiProvider ?
multiProviderDef(flags, providerAst.providers) :
singleProviderDef(flags, providerAst.providerType, providerAst.providers[0]);
return {
providerExpr,
flags: providerFlags, depsExpr,
tokenExpr: tokenExpr(providerAst.token),
};
}
function multiProviderDef(flags: NodeFlags, providers: CompileProviderMetadata[]):
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
const allDepDefs: o.Expression[] = [];
const allParams: o.FnParam[] = [];
const exprs = providers.map((provider, providerIndex) => {
let expr: o.Expression;
if (provider.useClass) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps);
expr = o.importExpr(provider.useClass).instantiate(depExprs);
} else if (provider.useFactory) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps);
expr = o.importExpr(provider.useFactory).callFn(depExprs);
} else if (provider.useExisting) {
const depExprs = convertDeps(providerIndex, [{token: provider.useExisting}]);
expr = depExprs[0];
} else {
expr = convertValueToOutputAst(provider.useValue);
}
return expr;
});
const providerExpr =
o.fn(allParams, [new o.ReturnStatement(o.literalArr(exprs))], o.INFERRED_TYPE);
return {
providerExpr,
flags: flags | NodeFlags.TypeFactoryProvider,
depsExpr: o.literalArr(allDepDefs)
};
function convertDeps(providerIndex: number, deps: CompileDiDependencyMetadata[]) {
return deps.map((dep, depIndex) => {
const paramName = `p${providerIndex}_${depIndex}`;
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
allDepDefs.push(depDef(dep));
return o.variable(paramName);
});
}
}
function singleProviderDef(
flags: NodeFlags, providerType: ProviderAstType, providerMeta: CompileProviderMetadata):
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
let providerExpr: o.Expression;
let deps: CompileDiDependencyMetadata[];
if (providerType === ProviderAstType.Directive || providerType === ProviderAstType.Component) {
providerExpr = o.importExpr(providerMeta.useClass !);
flags |= NodeFlags.TypeDirective;
deps = providerMeta.deps || providerMeta.useClass !.diDeps;
} else {
if (providerMeta.useClass) {
providerExpr = o.importExpr(providerMeta.useClass);
flags |= NodeFlags.TypeClassProvider;
deps = providerMeta.deps || providerMeta.useClass.diDeps;
} else if (providerMeta.useFactory) {
providerExpr = o.importExpr(providerMeta.useFactory);
flags |= NodeFlags.TypeFactoryProvider;
deps = providerMeta.deps || providerMeta.useFactory.diDeps;
} else if (providerMeta.useExisting) {
providerExpr = o.NULL_EXPR;
flags |= NodeFlags.TypeUseExistingProvider;
deps = [{token: providerMeta.useExisting}];
} else {
providerExpr = convertValueToOutputAst(providerMeta.useValue);
flags |= NodeFlags.TypeValueProvider;
deps = [];
}
}
const depsExpr = o.literalArr(deps.map(dep => depDef(dep)));
return {providerExpr, flags, depsExpr};
}
function tokenExpr(tokenMeta: CompileTokenMetadata): o.Expression {
return tokenMeta.identifier ? o.importExpr(tokenMeta.identifier) : o.literal(tokenMeta.value);
}
export function depDef(dep: CompileDiDependencyMetadata): o.Expression {
// Note: the following fields have already been normalized out by provider_analyzer:
// - isAttribute, isSelf, isHost
const expr = dep.isValue ? convertValueToOutputAst(dep.value) : tokenExpr(dep.token !);
let flags = DepFlags.None;
if (dep.isSkipSelf) {
flags |= DepFlags.SkipSelf;
}
if (dep.isOptional) {
flags |= DepFlags.Optional;
}
if (dep.isValue) {
flags |= DepFlags.Value;
}
return flags === DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]);
}
export function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags {
let nodeFlag = NodeFlags.None;
switch (lifecycleHook) {
case LifecycleHooks.AfterContentChecked:
nodeFlag = NodeFlags.AfterContentChecked;
break;
case LifecycleHooks.AfterContentInit:
nodeFlag = NodeFlags.AfterContentInit;
break;
case LifecycleHooks.AfterViewChecked:
nodeFlag = NodeFlags.AfterViewChecked;
break;
case LifecycleHooks.AfterViewInit:
nodeFlag = NodeFlags.AfterViewInit;
break;
case LifecycleHooks.DoCheck:
nodeFlag = NodeFlags.DoCheck;
break;
case LifecycleHooks.OnChanges:
nodeFlag = NodeFlags.OnChanges;
break;
case LifecycleHooks.OnDestroy:
nodeFlag = NodeFlags.OnDestroy;
break;
case LifecycleHooks.OnInit:
nodeFlag = NodeFlags.OnInit;
break;
}
return nodeFlag;
}
export function componentFactoryResolverProviderDef(
flags: NodeFlags, entryComponents: CompileEntryComponentMetadata[]): {
providerExpr: o.Expression,
flags: NodeFlags,
depsExpr: o.Expression,
tokenExpr: o.Expression
} {
const entryComponentFactories = entryComponents.map(
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
const classMeta = {
diDeps: [
{isValue: true, value: o.literalArr(entryComponentFactories)},
{token: token, isSkipSelf: true, isOptional: true},
{token: createIdentifierToken(Identifiers.NgModuleRef)},
],
lifecycleHooks: [],
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
};
const {providerExpr, flags: providerFlags, depsExpr} =
singleProviderDef(flags, ProviderAstType.PrivateService, {
token,
multi: false,
useClass: classMeta,
});
return {providerExpr, flags: providerFlags, depsExpr, tokenExpr: tokenExpr(token)};
}

View File

@ -21,6 +21,8 @@ import {ParseSourceSpan} from '../parse_util';
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {componentFactoryResolverProviderDef, depDef, lifecycleHookToNodeFlag, providerDef} from './provider_compiler';
const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style';
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
@ -96,10 +98,10 @@ interface UpdateExpression {
value: AST;
}
const LOG_VAR = o.variable('l');
const VIEW_VAR = o.variable('v');
const CHECK_VAR = o.variable('ck');
const COMP_VAR = o.variable('co');
const LOG_VAR = o.variable('_l');
const VIEW_VAR = o.variable('_v');
const CHECK_VAR = o.variable('_ck');
const COMP_VAR = o.variable('_co');
const EVENT_NAME_VAR = o.variable('en');
const ALLOW_DEFAULT_VAR = o.variable(`ad`);
@ -409,10 +411,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
const hostBindings:
{context: o.Expression, inputAst: BoundElementPropertyAst, dirAst: DirectiveAst}[] = [];
const hostEvents: {context: o.Expression, eventAst: BoundEventAst, dirAst: DirectiveAst}[] = [];
const componentFactoryResolverProvider = createComponentFactoryResolver(ast.directives);
if (componentFactoryResolverProvider) {
this._visitProvider(componentFactoryResolverProvider, ast.queryMatches);
}
this._visitComponentFactoryResolverProvider(ast.directives);
ast.providers.forEach((providerAst, providerIndex) => {
let dirAst: DirectiveAst = undefined !;
@ -585,47 +584,58 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
}
private _visitProvider(providerAst: ProviderAst, queryMatches: QueryMatch[]): void {
this._addProviderNode(this._visitProviderOrDirective(providerAst, queryMatches));
}
private _visitComponentFactoryResolverProvider(directives: DirectiveAst[]) {
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
const {providerExpr, depsExpr, flags, tokenExpr} = componentFactoryResolverProviderDef(
NodeFlags.PrivateProvider, componentDirMeta.directive.entryComponents);
this._addProviderNode({
providerExpr,
depsExpr,
flags,
tokenExpr,
queryMatchExprs: [],
sourceSpan: componentDirMeta.sourceSpan
});
}
}
private _addProviderNode(data: {
flags: NodeFlags,
queryMatchExprs: o.Expression[],
providerExpr: o.Expression,
depsExpr: o.Expression,
tokenExpr: o.Expression,
sourceSpan: ParseSourceSpan
}) {
const nodeIndex = this.nodes.length;
// reserve the space in the nodeDefs array so we can add children
this.nodes.push(null !);
const {flags, queryMatchExprs, providerExpr, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches);
// providerDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], token:any,
// value: any, deps: ([DepFlags, any] | any)[]): NodeDef;
this.nodes[nodeIndex] = () => ({
sourceSpan: providerAst.sourceSpan,
nodeFlags: flags,
nodeDef: o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
o.literal(flags), queryMatchExprs.length ? o.literalArr(queryMatchExprs) : o.NULL_EXPR,
tokenExpr(providerAst.token), providerExpr, depsExpr
])
});
this.nodes.push(
() => ({
sourceSpan: data.sourceSpan,
nodeFlags: data.flags,
nodeDef: o.importExpr(createIdentifier(Identifiers.providerDef)).callFn([
o.literal(data.flags),
data.queryMatchExprs.length ? o.literalArr(data.queryMatchExprs) : o.NULL_EXPR,
data.tokenExpr, data.providerExpr, data.depsExpr
])
}));
}
private _visitProviderOrDirective(providerAst: ProviderAst, queryMatches: QueryMatch[]): {
flags: NodeFlags,
tokenExpr: o.Expression,
sourceSpan: ParseSourceSpan,
queryMatchExprs: o.Expression[],
providerExpr: o.Expression,
depsExpr: o.Expression
} {
let flags = NodeFlags.None;
if (!providerAst.eager) {
flags |= NodeFlags.LazyProvider;
}
if (providerAst.providerType === ProviderAstType.PrivateService) {
flags |= NodeFlags.PrivateProvider;
}
providerAst.lifecycleHooks.forEach((lifecycleHook) => {
// for regular providers, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy ||
providerAst.providerType === ProviderAstType.Directive ||
providerAst.providerType === ProviderAstType.Component) {
flags |= lifecycleHookToNodeFlag(lifecycleHook);
}
});
let queryMatchExprs: o.Expression[] = [];
queryMatches.forEach((match) => {
@ -634,8 +644,15 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
o.literalArr([o.literal(match.queryId), o.literal(QueryValueType.Provider)]));
}
});
const {providerExpr, depsExpr, flags: providerType} = providerDef(providerAst);
return {flags: flags | providerType, queryMatchExprs, providerExpr, depsExpr};
const {providerExpr, depsExpr, flags: providerFlags, tokenExpr} = providerDef(providerAst);
return {
flags: flags | providerFlags,
queryMatchExprs,
providerExpr,
depsExpr,
tokenExpr,
sourceSpan: providerAst.sourceSpan
};
}
getLocal(name: string): o.Expression|null {
@ -885,100 +902,6 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
visitAttr(ast: AttrAst, context: any): any {}
}
function providerDef(providerAst: ProviderAst):
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
return providerAst.multiProvider ?
multiProviderDef(providerAst.providers) :
singleProviderDef(providerAst.providerType, providerAst.providers[0]);
}
function multiProviderDef(providers: CompileProviderMetadata[]):
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
const allDepDefs: o.Expression[] = [];
const allParams: o.FnParam[] = [];
const exprs = providers.map((provider, providerIndex) => {
let expr: o.Expression;
if (provider.useClass) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useClass.diDeps);
expr = o.importExpr(provider.useClass).instantiate(depExprs);
} else if (provider.useFactory) {
const depExprs = convertDeps(providerIndex, provider.deps || provider.useFactory.diDeps);
expr = o.importExpr(provider.useFactory).callFn(depExprs);
} else if (provider.useExisting) {
const depExprs = convertDeps(providerIndex, [{token: provider.useExisting}]);
expr = depExprs[0];
} else {
expr = convertValueToOutputAst(provider.useValue);
}
return expr;
});
const providerExpr =
o.fn(allParams, [new o.ReturnStatement(o.literalArr(exprs))], o.INFERRED_TYPE);
return {providerExpr, flags: NodeFlags.TypeFactoryProvider, depsExpr: o.literalArr(allDepDefs)};
function convertDeps(providerIndex: number, deps: CompileDiDependencyMetadata[]) {
return deps.map((dep, depIndex) => {
const paramName = `p${providerIndex}_${depIndex}`;
allParams.push(new o.FnParam(paramName, o.DYNAMIC_TYPE));
allDepDefs.push(depDef(dep));
return o.variable(paramName);
});
}
}
function singleProviderDef(providerType: ProviderAstType, providerMeta: CompileProviderMetadata):
{providerExpr: o.Expression, flags: NodeFlags, depsExpr: o.Expression} {
let providerExpr: o.Expression;
let flags: NodeFlags;
let deps: CompileDiDependencyMetadata[];
if (providerType === ProviderAstType.Directive || providerType === ProviderAstType.Component) {
providerExpr = o.importExpr(providerMeta.useClass !);
flags = NodeFlags.TypeDirective;
deps = providerMeta.deps || providerMeta.useClass !.diDeps;
} else {
if (providerMeta.useClass) {
providerExpr = o.importExpr(providerMeta.useClass);
flags = NodeFlags.TypeClassProvider;
deps = providerMeta.deps || providerMeta.useClass.diDeps;
} else if (providerMeta.useFactory) {
providerExpr = o.importExpr(providerMeta.useFactory);
flags = NodeFlags.TypeFactoryProvider;
deps = providerMeta.deps || providerMeta.useFactory.diDeps;
} else if (providerMeta.useExisting) {
providerExpr = o.NULL_EXPR;
flags = NodeFlags.TypeUseExistingProvider;
deps = [{token: providerMeta.useExisting}];
} else {
providerExpr = convertValueToOutputAst(providerMeta.useValue);
flags = NodeFlags.TypeValueProvider;
deps = [];
}
}
const depsExpr = o.literalArr(deps.map(dep => depDef(dep)));
return {providerExpr, flags, depsExpr};
}
function tokenExpr(tokenMeta: CompileTokenMetadata): o.Expression {
return tokenMeta.identifier ? o.importExpr(tokenMeta.identifier) : o.literal(tokenMeta.value);
}
function depDef(dep: CompileDiDependencyMetadata): o.Expression {
// Note: the following fields have already been normalized out by provider_analyzer:
// - isAttribute, isSelf, isHost
const expr = dep.isValue ? convertValueToOutputAst(dep.value) : tokenExpr(dep.token !);
let flags = DepFlags.None;
if (dep.isSkipSelf) {
flags |= DepFlags.SkipSelf;
}
if (dep.isOptional) {
flags |= DepFlags.Optional;
}
if (dep.isValue) {
flags |= DepFlags.Value;
}
return flags === DepFlags.None ? expr : o.literalArr([o.literal(flags), expr]);
}
function needsAdditionalRootNode(astNodes: TemplateAst[]): boolean {
const lastAstNode = astNodes[astNodes.length - 1];
if (lastAstNode instanceof EmbeddedTemplateAst) {
@ -995,36 +918,6 @@ function needsAdditionalRootNode(astNodes: TemplateAst[]): boolean {
return lastAstNode instanceof NgContentAst;
}
function lifecycleHookToNodeFlag(lifecycleHook: LifecycleHooks): NodeFlags {
let nodeFlag = NodeFlags.None;
switch (lifecycleHook) {
case LifecycleHooks.AfterContentChecked:
nodeFlag = NodeFlags.AfterContentChecked;
break;
case LifecycleHooks.AfterContentInit:
nodeFlag = NodeFlags.AfterContentInit;
break;
case LifecycleHooks.AfterViewChecked:
nodeFlag = NodeFlags.AfterViewChecked;
break;
case LifecycleHooks.AfterViewInit:
nodeFlag = NodeFlags.AfterViewInit;
break;
case LifecycleHooks.DoCheck:
nodeFlag = NodeFlags.DoCheck;
break;
case LifecycleHooks.OnChanges:
nodeFlag = NodeFlags.OnChanges;
break;
case LifecycleHooks.OnDestroy:
nodeFlag = NodeFlags.OnDestroy;
break;
case LifecycleHooks.OnInit:
nodeFlag = NodeFlags.OnInit;
break;
}
return nodeFlag;
}
function elementBindingDef(inputAst: BoundElementPropertyAst, dirAst: DirectiveAst): o.Expression {
switch (inputAst.type) {
@ -1147,30 +1040,6 @@ function staticViewQueryIds(nodeStaticQueryIds: Map<TemplateAst, StaticAndDynami
return {staticQueryIds, dynamicQueryIds};
}
function createComponentFactoryResolver(directives: DirectiveAst[]): ProviderAst|null {
const componentDirMeta = directives.find(dirAst => dirAst.directive.isComponent);
if (componentDirMeta && componentDirMeta.directive.entryComponents.length) {
const entryComponentFactories = componentDirMeta.directive.entryComponents.map(
(entryComponent) => o.importExpr({reference: entryComponent.componentFactory}));
const token = createIdentifierToken(Identifiers.ComponentFactoryResolver);
const classMeta: CompileTypeMetadata = {
diDeps: [
{isValue: true, value: o.literalArr(entryComponentFactories)},
{token: token, isSkipSelf: true, isOptional: true},
{token: createIdentifierToken(Identifiers.NgModuleRef)},
],
lifecycleHooks: [],
reference: resolveIdentifier(Identifiers.CodegenComponentFactoryResolver)
};
return new ProviderAst(
token, false, true, [{token, multi: false, useClass: classMeta}],
ProviderAstType.PrivateService, [], componentDirMeta.sourceSpan);
}
return null;
}
function elementEventNameAndTarget(
eventAst: BoundEventAst, dirAst: DirectiveAst | null): {name: string, target: string | null} {
if (eventAst.isAnimation) {