refactor: use view engine also for `NgModuleFactory`s (#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) {

View File

@ -0,0 +1,36 @@
/**
* @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 {async} from '@angular/core/testing';
import {MockDirectory, compile, expectNoDiagnostics, setup} from './test_util';
describe('regressions', () => {
let angularFiles = setup();
it('should compile components with empty templates', async(() => {
const appDir = {
'app.module.ts': `
import { Component, NgModule } from '@angular/core';
@Component({template: ''})
export class EmptyComp {}
@NgModule({declarations: [EmptyComp]})
export class MyModule {}
`
};
const rootDir = {'app': appDir};
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics}, {
noUnusedLocals: true,
noUnusedParameters: true
}).then((result) => {
expect(result.genFiles.find((f) => f.genFileUrl === '/app/app.module.ngfactory.ts'))
.toBeTruthy();
});
}));
});

View File

@ -600,12 +600,15 @@ function isSource(fileName: string): boolean {
return !/\.d\.ts$/.test(fileName) && /\.ts$/.test(fileName);
}
export function compile(rootDirs: MockData, options: {
emit?: boolean,
useSummaries?: boolean,
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {}): Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
export function compile(
rootDirs: MockData, options: {
emit?: boolean,
useSummaries?: boolean,
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {},
tsOptions: ts.CompilerOptions = {}):
Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
// Make sure we always return errors via the promise...
return Promise.resolve(null).then(() => {
// when using summaries, always emit so the next step can use the results.
@ -621,8 +624,9 @@ export function compile(rootDirs: MockData, options: {
aotHost.hideMetadata();
aotHost.tsFilesOnly();
}
const tsSettings = {...settings, ...tsOptions};
const scripts = host.scriptNames.slice(0);
const program = ts.createProgram(scripts, settings, host);
const program = ts.createProgram(scripts, tsSettings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)).then(genFiles => {
@ -630,7 +634,7 @@ export function compile(rootDirs: MockData, options: {
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
host.override(file.genFileUrl, file.source));
const scripts = host.scriptNames.slice(0);
const newProgram = ts.createProgram(scripts, settings, host);
const newProgram = ts.createProgram(scripts, tsSettings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();

View File

@ -977,8 +977,8 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {providers: [provider]});
const elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
expect(elAst.providers.length).toBe(2);
expect(elAst.providers[1].providerType).toBe(ProviderAstType.PublicService);
expect(elAst.providers[1].providers).toEqual([provider]);
expect(elAst.providers[0].providerType).toBe(ProviderAstType.PublicService);
expect(elAst.providers[0].providers).toEqual([provider]);
});
it('should use the private providers of a component', () => {
@ -986,8 +986,8 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const comp = createDir('my-comp', {viewProviders: [provider]});
const elAst: ElementAst = <ElementAst>parse('<my-comp>', [comp])[0];
expect(elAst.providers.length).toBe(2);
expect(elAst.providers[1].providerType).toBe(ProviderAstType.PrivateService);
expect(elAst.providers[1].providers).toEqual([provider]);
expect(elAst.providers[0].providerType).toBe(ProviderAstType.PrivateService);
expect(elAst.providers[0].providers).toEqual([provider]);
});
it('should support multi providers', () => {
@ -998,8 +998,8 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirB = createDir('[dirB]', {providers: [provider2]});
const elAst: ElementAst = <ElementAst>parse('<div dirA dirB>', [dirA, dirB])[0];
expect(elAst.providers.length).toBe(4);
expect(elAst.providers[2].providers).toEqual([provider0, provider2]);
expect(elAst.providers[3].providers).toEqual([provider1]);
expect(elAst.providers[0].providers).toEqual([provider0, provider2]);
expect(elAst.providers[1].providers).toEqual([provider1]);
});
it('should overwrite non multi providers', () => {
@ -1010,8 +1010,8 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirB = createDir('[dirB]', {providers: [provider3]});
const elAst: ElementAst = <ElementAst>parse('<div dirA dirB>', [dirA, dirB])[0];
expect(elAst.providers.length).toBe(4);
expect(elAst.providers[2].providers).toEqual([provider3]);
expect(elAst.providers[3].providers).toEqual([provider2]);
expect(elAst.providers[0].providers).toEqual([provider3]);
expect(elAst.providers[1].providers).toEqual([provider2]);
});
it('should overwrite component providers by directive providers', () => {
@ -1021,7 +1021,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {providers: [dirProvider]});
const elAst: ElementAst = <ElementAst>parse('<my-comp dirA>', [dirA, comp])[0];
expect(elAst.providers.length).toBe(3);
expect(elAst.providers[2].providers).toEqual([dirProvider]);
expect(elAst.providers[0].providers).toEqual([dirProvider]);
});
it('should overwrite view providers by directive providers', () => {
@ -1031,7 +1031,7 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {providers: [dirProvider]});
const elAst: ElementAst = <ElementAst>parse('<my-comp dirA>', [dirA, comp])[0];
expect(elAst.providers.length).toBe(3);
expect(elAst.providers[2].providers).toEqual([dirProvider]);
expect(elAst.providers[0].providers).toEqual([dirProvider]);
});
it('should overwrite directives by providers', () => {
@ -1053,17 +1053,17 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
`Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]<div dirA dirB>"): TestComp@0:0`);
});
it('should sort providers by their DI order', () => {
it('should sort providers by their DI order, lazy providers first', () => {
const provider0 = createProvider('service0', {deps: ['type:[dir2]']});
const provider1 = createProvider('service1');
const dir2 = createDir('[dir2]', {deps: ['service1']});
const comp = createDir('my-comp', {providers: [provider0, provider1]});
const elAst: ElementAst = <ElementAst>parse('<my-comp dir2>', [comp, dir2])[0];
expect(elAst.providers.length).toBe(4);
expect(elAst.providers[0].providers[0].useClass).toEqual(comp.type);
expect(elAst.providers[1].providers).toEqual([provider1]);
expect(elAst.providers[2].providers[0].useClass).toEqual(dir2.type);
expect(elAst.providers[3].providers).toEqual([provider0]);
expect(elAst.providers[1].providers[0].useClass).toEqual(comp.type);
expect(elAst.providers[2].providers).toEqual([provider1]);
expect(elAst.providers[3].providers[0].useClass).toEqual(dir2.type);
expect(elAst.providers[0].providers).toEqual([provider0]);
});
it('should sort directives by their DI order', () => {
@ -1086,12 +1086,12 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const dirA = createDir('[dirA]', {providers: [provider0, provider1], deps: ['service0']});
const elAst: ElementAst = <ElementAst>parse('<div dirA>', [dirA])[0];
expect(elAst.providers.length).toBe(3);
expect(elAst.providers[0].providers).toEqual([provider0]);
expect(elAst.providers[0].eager).toBe(true);
expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[1].providers).toEqual([provider0]);
expect(elAst.providers[1].eager).toBe(true);
expect(elAst.providers[2].providers).toEqual([provider1]);
expect(elAst.providers[2].eager).toBe(false);
expect(elAst.providers[2].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[2].eager).toBe(true);
expect(elAst.providers[0].providers).toEqual([provider1]);
expect(elAst.providers[0].eager).toBe(false);
});
it('should mark dependencies on parent elements as eager', () => {
@ -1102,12 +1102,12 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const elAst: ElementAst =
<ElementAst>parse('<div dirA><div dirB></div></div>', [dirA, dirB])[0];
expect(elAst.providers.length).toBe(3);
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[0].eager).toBe(true);
expect(elAst.providers[1].providers).toEqual([provider0]);
expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[1].eager).toBe(true);
expect(elAst.providers[2].providers).toEqual([provider1]);
expect(elAst.providers[2].eager).toBe(false);
expect(elAst.providers[2].providers).toEqual([provider0]);
expect(elAst.providers[2].eager).toBe(true);
expect(elAst.providers[0].providers).toEqual([provider1]);
expect(elAst.providers[0].eager).toBe(false);
});
it('should mark queried providers as eager', () => {
@ -1117,12 +1117,12 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
createDir('[dirA]', {providers: [provider0, provider1], queries: ['service0']});
const elAst: ElementAst = <ElementAst>parse('<div dirA></div>', [dirA])[0];
expect(elAst.providers.length).toBe(3);
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[0].eager).toBe(true);
expect(elAst.providers[1].providers).toEqual([provider0]);
expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[1].eager).toBe(true);
expect(elAst.providers[2].providers).toEqual([provider1]);
expect(elAst.providers[2].eager).toBe(false);
expect(elAst.providers[2].providers).toEqual([provider0]);
expect(elAst.providers[2].eager).toBe(true);
expect(elAst.providers[0].providers).toEqual([provider1]);
expect(elAst.providers[0].eager).toBe(false);
});
it('should not mark dependencies across embedded views as eager', () => {
@ -1132,10 +1132,10 @@ Binding to attribute 'onEvent' is disallowed for security reasons ("<my-componen
const elAst: ElementAst =
<ElementAst>parse('<div dirA><div *ngIf dirB></div></div>', [dirA, dirB])[0];
expect(elAst.providers.length).toBe(2);
expect(elAst.providers[0].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[0].eager).toBe(true);
expect(elAst.providers[1].providers).toEqual([provider0]);
expect(elAst.providers[1].eager).toBe(false);
expect(elAst.providers[1].providers[0].useClass).toEqual(dirA.type);
expect(elAst.providers[1].eager).toBe(true);
expect(elAst.providers[0].providers).toEqual([provider0]);
expect(elAst.providers[0].eager).toBe(false);
});
it('should report missing @Self() deps as errors', () => {

View File

@ -23,7 +23,7 @@ import {Injectable, InjectionToken, Injector, Provider, ReflectiveInjector} from
import {CompilerFactory, CompilerOptions} from './linker/compiler';
import {ComponentFactory, ComponentRef} from './linker/component_factory';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from './linker/component_factory_resolver';
import {NgModuleFactory, NgModuleInjector, NgModuleRef} from './linker/ng_module_factory';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from './linker/ng_module_factory';
import {InternalViewRef, ViewRef} from './linker/view_ref';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from './profile/profile';
import {Testability, TestabilityRegistry} from './testability/testability';
@ -293,7 +293,7 @@ export class PlatformRef_ extends PlatformRef {
return ngZone.run(() => {
const ngZoneInjector =
ReflectiveInjector.resolveAndCreate([{provide: NgZone, useValue: ngZone}], this.injector);
const moduleRef = <NgModuleInjector<M>>moduleFactory.create(ngZoneInjector);
const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
@ -326,10 +326,10 @@ export class PlatformRef_ extends PlatformRef {
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
}
private _moduleDoBootstrap(moduleRef: NgModuleInjector<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef);
if (moduleRef.bootstrapFactories.length > 0) {
moduleRef.bootstrapFactories.forEach(f => appRef.bootstrap(f));
private _moduleDoBootstrap(moduleRef: InternalNgModuleRef<any>): void {
const appRef = moduleRef.injector.get(ApplicationRef) as ApplicationRef;
if (moduleRef._bootstrapComponents.length > 0) {
moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
} else if (moduleRef.instance.ngDoBootstrap) {
moduleRef.instance.ngDoBootstrap(appRef);
} else {

View File

@ -7,7 +7,6 @@
*/
export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} from './linker/component_factory_resolver';
export {NgModuleInjector as ɵNgModuleInjector} from './linker/ng_module_factory';
export {registerModuleFactory as ɵregisterModuleFactory} from './linker/ng_module_factory_loader';
export {reflector as ɵreflector} from './reflection/reflection';
export {ArgumentType as ɵArgumentType, BindingFlags as ɵBindingFlags, DepFlags as ɵDepFlags, EMPTY_ARRAY as ɵEMPTY_ARRAY, EMPTY_MAP as ɵEMPTY_MAP, NodeFlags as ɵNodeFlags, QueryBindingType as ɵQueryBindingType, QueryValueType as ɵQueryValueType, ViewDefinition as ɵViewDefinition, ViewFlags as ɵViewFlags, anchorDef as ɵand, createComponentFactory as ɵccf, createRendererType2 as ɵcrt, directiveDef as ɵdid, elementDef as ɵeld, elementEventFullName as ɵelementEventFullName, getComponentViewDefinitionFactory as ɵgetComponentViewDefinitionFactory, inlineInterpolate as ɵinlineInterpolate, interpolate as ɵinterpolate, ngContentDef as ɵncd, nodeValue as ɵnov, pipeDef as ɵpid, providerDef as ɵprd, pureArrayDef as ɵpad, pureObjectDef as ɵpod, purePipeDef as ɵppd, queryDef as ɵqud, textDef as ɵted, unwrapValue as ɵunv, viewDef as ɵvid} from './view/index';
export {ArgumentType as ɵArgumentType, BindingFlags as ɵBindingFlags, DepFlags as ɵDepFlags, EMPTY_ARRAY as ɵEMPTY_ARRAY, EMPTY_MAP as ɵEMPTY_MAP, NodeFlags as ɵNodeFlags, QueryBindingType as ɵQueryBindingType, QueryValueType as ɵQueryValueType, ViewDefinition as ɵViewDefinition, ViewFlags as ɵViewFlags, anchorDef as ɵand, createComponentFactory as ɵccf, createNgModuleFactory as ɵcmf, createRendererType2 as ɵcrt, directiveDef as ɵdid, elementDef as ɵeld, elementEventFullName as ɵelementEventFullName, getComponentViewDefinitionFactory as ɵgetComponentViewDefinitionFactory, inlineInterpolate as ɵinlineInterpolate, interpolate as ɵinterpolate, moduleDef as ɵmod, moduleProvideDef as ɵmpd, ngContentDef as ɵncd, nodeValue as ɵnov, pipeDef as ɵpid, providerDef as ɵprd, pureArrayDef as ɵpad, pureObjectDef as ɵpod, purePipeDef as ɵppd, queryDef as ɵqud, textDef as ɵted, unwrapValue as ɵunv, viewDef as ɵvid} from './view/index';

View File

@ -39,20 +39,22 @@ function constructResolvingPath(keys: any[]): string {
export interface InjectionError extends Error {
keys: ReflectiveKey[];
injectors: ReflectiveInjector[];
constructResolvingMessage: (this: InjectionError) => string;
constructResolvingMessage: (keys: ReflectiveKey[]) => string;
addKey(injector: ReflectiveInjector, key: ReflectiveKey): void;
}
function injectionError(
injector: ReflectiveInjector, key: ReflectiveKey,
constructResolvingMessage: (this: InjectionError) => string,
constructResolvingMessage: (keys: ReflectiveKey[]) => string,
originalError?: Error): InjectionError {
const error = (originalError ? wrappedError('', originalError) : Error()) as InjectionError;
const keys = [key];
const errMsg = constructResolvingMessage(keys);
const error =
(originalError ? wrappedError(errMsg, originalError) : Error(errMsg)) as InjectionError;
error.addKey = addKey;
error.keys = [key];
error.keys = keys;
error.injectors = [injector];
error.constructResolvingMessage = constructResolvingMessage;
error.message = error.constructResolvingMessage();
(error as any)[ERROR_ORIGINAL_ERROR] = originalError;
return error;
}
@ -60,7 +62,8 @@ function injectionError(
function addKey(this: InjectionError, injector: ReflectiveInjector, key: ReflectiveKey): void {
this.injectors.push(injector);
this.keys.push(key);
this.message = this.constructResolvingMessage();
// Note: This updated message won't be reflected in the `.stack` property
this.message = this.constructResolvingMessage(this.keys);
}
/**
@ -78,9 +81,9 @@ function addKey(this: InjectionError, injector: ReflectiveInjector, key: Reflect
* ```
*/
export function noProviderError(injector: ReflectiveInjector, key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
const first = stringify(this.keys[0].token);
return `No provider for ${first}!${constructResolvingPath(this.keys)}`;
return injectionError(injector, key, function(keys: ReflectiveKey[]) {
const first = stringify(keys[0].token);
return `No provider for ${first}!${constructResolvingPath(keys)}`;
});
}
@ -102,8 +105,8 @@ export function noProviderError(injector: ReflectiveInjector, key: ReflectiveKey
*/
export function cyclicDependencyError(
injector: ReflectiveInjector, key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
return `Cannot instantiate cyclic dependency!${constructResolvingPath(this.keys)}`;
return injectionError(injector, key, function(keys: ReflectiveKey[]) {
return `Cannot instantiate cyclic dependency!${constructResolvingPath(keys)}`;
});
}
@ -136,9 +139,9 @@ export function cyclicDependencyError(
export function instantiationError(
injector: ReflectiveInjector, originalException: any, originalStack: any,
key: ReflectiveKey): InjectionError {
return injectionError(injector, key, function(this: InjectionError) {
const first = stringify(this.keys[0].token);
return `${getOriginalError(this).message}: Error during instantiation of ${first}!${constructResolvingPath(this.keys)}.`;
return injectionError(injector, key, function(keys: ReflectiveKey[]) {
const first = stringify(keys[0].token);
return `${originalException.message}: Error during instantiation of ${first}!${constructResolvingPath(keys)}.`;
}, originalException);
}

View File

@ -54,8 +54,13 @@ export class CodegenComponentFactoryResolver implements ComponentFactoryResolver
}
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> {
let factory = this._factories.get(component) || this._parent.resolveComponentFactory(component);
let factory = this._factories.get(component);
if (!factory && this._parent) {
factory = this._parent.resolveComponentFactory(component);
}
if (!factory) {
throw noComponentFactoryError(component);
}
return new ComponentFactoryBoundToModule(factory, this._ngModule);
}
}

View File

@ -6,12 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {Injector} from '../di/injector';
import {Type} from '../type';
import {stringify} from '../util';
import {ComponentFactory} from './component_factory';
import {CodegenComponentFactoryResolver, ComponentFactoryBoundToModule, ComponentFactoryResolver} from './component_factory_resolver';
import {ComponentFactoryResolver} from './component_factory_resolver';
/**
@ -50,76 +48,16 @@ export abstract class NgModuleRef<T> {
abstract onDestroy(callback: () => void): void;
}
export interface InternalNgModuleRef<T> extends NgModuleRef<T> {
// Note: we are using the prefix _ as NgModuleData is an NgModuleRef and therefore directly
// exposed to the user.
_bootstrapComponents: Type<any>[];
}
/**
* @experimental
*/
export class NgModuleFactory<T> {
constructor(
private _injectorClass: {new (parentInjector: Injector): NgModuleInjector<T>},
private _moduleType: Type<T>) {}
get moduleType(): Type<T> { return this._moduleType; }
create(parentInjector: Injector|null): NgModuleRef<T> {
const instance = new this._injectorClass(parentInjector || Injector.NULL);
instance.create();
return instance;
}
}
const _UNDEFINED = new Object();
export abstract class NgModuleInjector<T> implements Injector, NgModuleRef<T> {
bootstrapFactories: ComponentFactory<any>[];
instance: T;
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
private _cmpFactoryResolver: CodegenComponentFactoryResolver;
constructor(
public parent: Injector, factories: ComponentFactory<any>[],
bootstrapFactories: ComponentFactory<any>[]) {
this.bootstrapFactories =
bootstrapFactories.map(f => new ComponentFactoryBoundToModule(f, this));
this._cmpFactoryResolver = new CodegenComponentFactoryResolver(
factories, parent.get(ComponentFactoryResolver, ComponentFactoryResolver.NULL), this);
}
create() { this.instance = this.createInternal(); }
abstract createInternal(): T;
get(token: any, notFoundValue: any = THROW_IF_NOT_FOUND): any {
if (token === Injector || token === NgModuleRef) {
return this;
}
if (token === ComponentFactoryResolver) {
return this._cmpFactoryResolver;
}
const result = this.getInternal(token, _UNDEFINED);
return result === _UNDEFINED ? this.parent.get(token, notFoundValue) : result;
}
abstract getInternal(token: any, notFoundValue: any): any;
get injector(): Injector { return this; }
get componentFactoryResolver(): ComponentFactoryResolver { return this._cmpFactoryResolver; }
destroy(): void {
if (this._destroyed) {
throw new Error(
`The ng module ${stringify(this.instance.constructor)} has already been destroyed.`);
}
this._destroyed = true;
this.destroyInternal();
this._destroyListeners.forEach((listener) => listener());
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
abstract destroyInternal(): void;
export abstract class NgModuleFactory<T> {
abstract get moduleType(): Type<T>;
abstract create(parentInjector: Injector|null): NgModuleRef<T>;
}

View File

@ -10,7 +10,7 @@ import {RendererType2} from '../render/api';
import {SecurityContext} from '../security';
import {BindingDef, BindingFlags, ElementData, ElementHandleEventFn, NodeDef, NodeFlags, OutputDef, OutputType, QueryValueType, ViewData, ViewDefinitionFactory, asElementData} from './types';
import {NOOP, calcBindingFlags, checkAndUpdateBinding, dispatchEvent, elementEventFullName, getParentRenderElement, resolveRendererType2, resolveViewDefinition, splitMatchedQueriesDsl, splitNamespace} from './util';
import {NOOP, calcBindingFlags, checkAndUpdateBinding, dispatchEvent, elementEventFullName, getParentRenderElement, resolveDefinition, resolveRendererType2, splitMatchedQueriesDsl, splitNamespace} from './util';
export function anchorDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
@ -18,7 +18,7 @@ export function anchorDef(
templateFactory?: ViewDefinitionFactory): NodeDef {
flags |= NodeFlags.TypeElement;
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
const template = templateFactory ? resolveViewDefinition(templateFactory) : null;
const template = templateFactory ? resolveDefinition(templateFactory) : null;
return {
// will bet set by the view definition

View File

@ -8,13 +8,15 @@
export {anchorDef, elementDef} from './element';
export {ngContentDef} from './ng_content';
export {moduleDef, moduleProvideDef} from './ng_module';
export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query';
export {ViewRef_, createComponentFactory, getComponentViewDefinitionFactory, nodeValue} from './refs';
export {createNgModuleFactory} from './refs';
export {initServicesIfNeeded} from './services';
export {textDef} from './text';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, unwrapValue} from './util';
export {EMPTY_ARRAY, EMPTY_MAP, createRendererType2, elementEventFullName, inlineInterpolate, interpolate, rootRenderNodes, tokenKey, unwrapValue} from './util';
export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';

View File

@ -0,0 +1,184 @@
/**
* @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 {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {NgModuleRef} from '../linker/ng_module_factory';
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NgModuleProviderDef, NodeFlags} from './types';
import {tokenKey} from './util';
const NOT_CREATED = new Object();
const InjectorRefTokenKey = tokenKey(Injector);
const NgModuleRefTokenKey = tokenKey(NgModuleRef);
export function moduleProvideDef(
flags: NodeFlags, token: any, value: any,
deps: ([DepFlags, any] | any)[]): NgModuleProviderDef {
const depDefs: DepDef[] = deps.map(value => {
let token: any;
let flags: DepFlags;
if (Array.isArray(value)) {
[flags, token] = value;
} else {
flags = DepFlags.None;
token = value;
}
return {flags, token, tokenKey: tokenKey(token)};
});
return {
// will bet set by the module definition
index: -1,
deps: depDefs, flags, token, value
};
}
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition {
const providersByKey: {[key: string]: NgModuleProviderDef} = {};
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
provider.index = i;
providersByKey[tokenKey(provider.token)] = provider;
}
return {
// Will be filled later...
factory: null,
providersByKey,
providers
};
}
export function initNgModule(data: NgModuleData) {
const def = data._def;
const providers = data._providers = new Array(def.providers.length);
for (let i = 0; i < def.providers.length; i++) {
const provDef = def.providers[i];
providers[i] = provDef.flags & NodeFlags.LazyProvider ? NOT_CREATED :
_createProviderInstance(data, provDef);
}
}
export function resolveNgModuleDep(
data: NgModuleData, depDef: DepDef, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
if (depDef.flags & DepFlags.Value) {
return depDef.token;
}
if (depDef.flags & DepFlags.Optional) {
notFoundValue = null;
}
if (depDef.flags & DepFlags.SkipSelf) {
return data._parent.get(depDef.token, notFoundValue);
}
const tokenKey = depDef.tokenKey;
switch (tokenKey) {
case InjectorRefTokenKey:
case NgModuleRefTokenKey:
return data;
}
const providerDef = data._def.providersByKey[tokenKey];
if (providerDef) {
let providerInstance = data._providers[providerDef.index];
if (providerInstance === NOT_CREATED) {
providerInstance = data._providers[providerDef.index] =
_createProviderInstance(data, providerDef);
}
return providerInstance;
}
return data._parent.get(depDef.token, notFoundValue);
}
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any {
let injectable: any;
switch (providerDef.flags & NodeFlags.Types) {
case NodeFlags.TypeClassProvider:
injectable = _createClass(ngModule, providerDef !.value, providerDef !.deps);
break;
case NodeFlags.TypeFactoryProvider:
injectable = _callFactory(ngModule, providerDef !.value, providerDef !.deps);
break;
case NodeFlags.TypeUseExistingProvider:
injectable = resolveNgModuleDep(ngModule, providerDef !.deps[0]);
break;
case NodeFlags.TypeValueProvider:
injectable = providerDef !.value;
break;
}
return injectable;
}
function _createClass(ngModule: NgModuleData, ctor: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = new ctor();
break;
case 1:
injectable = new ctor(resolveNgModuleDep(ngModule, deps[0]));
break;
case 2:
injectable =
new ctor(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]));
break;
case 3:
injectable = new ctor(
resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]),
resolveNgModuleDep(ngModule, deps[2]));
break;
default:
const depValues = new Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveNgModuleDep(ngModule, deps[i]);
}
injectable = new ctor(...depValues);
}
return injectable;
}
function _callFactory(ngModule: NgModuleData, factory: any, deps: DepDef[]): any {
const len = deps.length;
let injectable: any;
switch (len) {
case 0:
injectable = factory();
break;
case 1:
injectable = factory(resolveNgModuleDep(ngModule, deps[0]));
break;
case 2:
injectable =
factory(resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]));
break;
case 3:
injectable = factory(
resolveNgModuleDep(ngModule, deps[0]), resolveNgModuleDep(ngModule, deps[1]),
resolveNgModuleDep(ngModule, deps[2]));
break;
default:
const depValues = Array(len);
for (let i = 0; i < len; i++) {
depValues[i] = resolveNgModuleDep(ngModule, deps[i]);
}
injectable = factory(...depValues);
}
return injectable;
}
export function callNgModuleLifecycle(ngModule: NgModuleData, lifecycles: NodeFlags) {
const def = ngModule._def;
for (let i = 0; i < def.providers.length; i++) {
const provDef = def.providers[i];
if (provDef.flags & NodeFlags.OnDestroy) {
const instance = ngModule._providers[i];
if (instance && instance !== NOT_CREATED) {
instance.ngOnDestroy();
}
}
}
}

View File

@ -105,7 +105,7 @@ export function _def(
ngContentIndex: -1, childCount, bindings,
bindingFlags: calcBindingFlags(bindings), outputs,
element: null,
provider: {token, tokenKey: tokenKey(token), value, deps: depDefs},
provider: {token, value, deps: depDefs},
text: null,
query: null,
ngContent: null

View File

@ -8,20 +8,22 @@
import {ApplicationRef} from '../application_ref';
import {ChangeDetectorRef} from '../change_detection/change_detection';
import {Injector} from '../di';
import {Injector} from '../di/injector';
import {ComponentFactory, ComponentRef} from '../linker/component_factory';
import {ComponentFactoryBoundToModule} from '../linker/component_factory_resolver';
import {ComponentFactoryBoundToModule, ComponentFactoryResolver} from '../linker/component_factory_resolver';
import {ElementRef} from '../linker/element_ref';
import {NgModuleRef} from '../linker/ng_module_factory';
import {InternalNgModuleRef, NgModuleFactory, NgModuleRef} from '../linker/ng_module_factory';
import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref';
import {EmbeddedViewRef, InternalViewRef, ViewRef} from '../linker/view_ref';
import {Renderer as RendererV1, Renderer2} from '../render/api';
import {Type} from '../type';
import {stringify} from '../util';
import {VERSION} from '../version';
import {DepFlags, ElementData, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {markParentViewsForCheck, resolveViewDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {callNgModuleLifecycle, initNgModule, resolveNgModuleDep} from './ng_module';
import {DepFlags, ElementData, NgModuleData, NgModuleDefinition, NgModuleDefinitionFactory, NodeDef, NodeFlags, Services, TemplateData, ViewContainerData, ViewData, ViewDefinitionFactory, ViewState, asElementData, asProviderData, asTextData} from './types';
import {markParentViewsForCheck, resolveDefinition, rootRenderNodes, splitNamespace, tokenKey, viewParentEl} from './util';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView, renderDetachView} from './view_attach';
const EMPTY_CONTEXT = new Object();
@ -41,6 +43,14 @@ export function getComponentViewDefinitionFactory(componentFactory: ComponentFac
return (componentFactory as ComponentFactory_).viewDefFactory;
}
// Attention: this function is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
export function createNgModuleFactory(
ngModuleType: Type<any>, bootstrapComponents: Type<any>[],
defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> {
return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory);
}
class ComponentFactory_ extends ComponentFactory<any> {
/**
* @internal
@ -85,7 +95,7 @@ class ComponentFactory_ extends ComponentFactory<any> {
if (!ngModule) {
throw new Error('ngModule should be provided');
}
const viewDef = resolveViewDefinition(this.viewDefFactory);
const viewDef = resolveDefinition(this.viewDefFactory);
const componentNodeIndex = viewDef.nodes[0].element !.componentProvider !.index;
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);
@ -452,3 +462,57 @@ class RendererAdapter implements RendererV1 {
animate(): any { throw new Error('Renderer.animate is no longer supported!'); }
}
class NgModuleFactory_ extends NgModuleFactory<any> {
constructor(
private _moduleType: Type<any>, private _bootstrapComponents: Type<any>[],
private _ngModuleDefFactory: NgModuleDefinitionFactory, ) {
// Attention: this ctor is called as top level function.
// Putting any logic in here will destroy closure tree shaking!
super();
}
get moduleType(): Type<any> { return this._moduleType; }
create(parentInjector: Injector|null): NgModuleRef<any> {
const def = resolveDefinition(this._ngModuleDefFactory);
return new NgModuleRef_(
this._moduleType, parentInjector || Injector.NULL, this._bootstrapComponents, def);
}
}
class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
private _destroyListeners: (() => void)[] = [];
private _destroyed: boolean = false;
public _providers: any[];
constructor(
private _moduleType: any, public _parent: Injector, public _bootstrapComponents: Type<any>[],
public _def: NgModuleDefinition) {
initNgModule(this);
}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
return resolveNgModuleDep(
this, {token: token, tokenKey: tokenKey(token), flags: DepFlags.None}, notFoundValue);
}
get instance() { return this.get(this._moduleType); }
get componentFactoryResolver() { return this.get(ComponentFactoryResolver); }
get injector(): Injector { return this; }
destroy(): void {
if (this._destroyed) {
throw new Error(
`The ng module ${stringify(this.instance.constructor)} has already been destroyed.`);
}
this._destroyed = true;
callNgModuleLifecycle(this, NodeFlags.OnDestroy);
this._destroyListeners.forEach((listener) => listener());
}
onDestroy(callback: () => void): void { this._destroyListeners.push(callback); }
}

View File

@ -19,8 +19,32 @@ import {Sanitizer, SecurityContext} from '../security';
// Defs
// -------------------------------------
export interface ViewDefinition {
factory: ViewDefinitionFactory|null;
/**
* Factory for ViewDefinitions/NgModuleDefinitions.
* We use a function so we can reexeute it in case an error happens and use the given logger
* function to log the error from the definition of the node, which is shown in all browser
* logs.
*/
export interface DefinitionFactory<D extends Definition<any>> { (logger: NodeLogger): D; }
/**
* Function to call console.error at the right source location. This is an indirection
* via another function as browser will log the location that actually called
* `console.error`.
*/
export interface NodeLogger { (): () => void; }
export interface Definition<DF extends DefinitionFactory<any>> { factory: DF|null; }
export interface NgModuleDefinition extends Definition<NgModuleDefinitionFactory> {
providers: NgModuleProviderDef[];
providersByKey: {[tokenKey: string]: NgModuleProviderDef};
}
export interface NgModuleDefinitionFactory extends DefinitionFactory<NgModuleDefinition> {}
;
export interface ViewDefinition extends Definition<ViewDefinitionFactory> {
flags: ViewFlags;
updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
@ -44,20 +68,8 @@ export interface ViewDefinition {
nodeMatchedQueries: number;
}
/**
* Factory for ViewDefinitions.
* We use a function so we can reexeute it in case an error happens and use the given logger
* function to log the error from the definition of the node, which is shown in all browser
* logs.
*/
export interface ViewDefinitionFactory { (logger: NodeLogger): ViewDefinition; }
export interface ViewDefinitionFactory extends DefinitionFactory<ViewDefinition> {}
/**
* Function to call console.error at the right source location. This is an indirection
* via another function as browser will log the location that actually called
* `console.error`.
*/
export interface NodeLogger { (): () => void; }
export interface ViewUpdateFn { (check: NodeCheckFn, view: ViewData): void; }
@ -245,7 +257,14 @@ export interface ElementHandleEventFn { (view: ViewData, eventName: string, even
export interface ProviderDef {
token: any;
tokenKey: string;
value: any;
deps: DepDef[];
}
export interface NgModuleProviderDef {
flags: NodeFlags;
index: number;
token: any;
value: any;
deps: DepDef[];
}
@ -296,6 +315,14 @@ export interface NgContentDef {
// Data
// -------------------------------------
export interface NgModuleData extends Injector, NgModuleRef<any> {
// Note: we are using the prefix _ as NgModuleData is an NgModuleRef and therefore directly
// exposed to the user.
_def: NgModuleDefinition;
_parent: Injector;
_providers: any[];
}
/**
* View instance data.
* Attention: Adding fields to this is performance sensitive!
@ -379,13 +406,20 @@ export interface ElementData {
template: TemplateData;
}
export interface ViewContainerData extends ViewContainerRef { _embeddedViews: ViewData[]; }
export interface ViewContainerData extends ViewContainerRef {
// Note: we are using the prefix _ as ViewContainerData is a ViewContainerRef and therefore
// directly
// exposed to the user.
_embeddedViews: ViewData[];
}
export interface TemplateData extends TemplateRef<any> {
// views that have been created from the template
// of this element,
// but inserted into the embeddedViews of another element.
// By default, this is undefined.
// Note: we are using the prefix _ as TemplateData is a TemplateRef and therefore directly
// exposed to the user.
_projectedViews: ViewData[];
}

View File

@ -12,7 +12,7 @@ import {RendererType2} from '../render/api';
import {looseIdentical, stringify} from '../util';
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {BindingDef, BindingFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
import {BindingDef, BindingFlags, Definition, DefinitionFactory, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
export const NOOP: any = () => {};
@ -220,14 +220,14 @@ export function getParentRenderElement(view: ViewData, renderHost: any, def: Nod
}
}
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
const DEFINITION_CACHE = new WeakMap<any, Definition<any>>();
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory) !;
export function resolveDefinition<D extends Definition<any>>(factory: DefinitionFactory<D>): D {
let value = DEFINITION_CACHE.get(factory) !as D;
if (!value) {
value = factory(() => NOOP);
value.factory = factory;
VIEW_DEFINITION_CACHE.set(factory, value);
DEFINITION_CACHE.set(factory, value);
}
return value;
}

View File

@ -17,7 +17,7 @@ import {checkAndUpdateQuery, createQuery} from './query';
import {createTemplateData, createViewContainerData} from './refs';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveDefinition, tokenKey} from './util';
import {detachProjectedView} from './view_attach';
export function viewDef(
@ -102,7 +102,7 @@ export function viewDef(
const isPrivateService = (node.flags & NodeFlags.PrivateProvider) !== 0;
const isComponent = (node.flags & NodeFlags.Component) !== 0;
if (!isPrivateService || isComponent) {
currentParent !.element !.publicProviders ![node.provider !.tokenKey] = node;
currentParent !.element !.publicProviders ![tokenKey(node.provider !.token)] = node;
} else {
if (!currentElementHasPrivateProviders) {
currentElementHasPrivateProviders = true;
@ -110,7 +110,7 @@ export function viewDef(
currentParent !.element !.allProviders =
Object.create(currentParent !.element !.publicProviders);
}
currentParent !.element !.allProviders ![node.provider !.tokenKey] = node;
currentParent !.element !.allProviders ![tokenKey(node.provider !.token)] = node;
}
if (isComponent) {
currentParent !.element !.componentProvider = node;
@ -240,7 +240,7 @@ function createViewNodes(view: ViewData) {
const el = createElement(view, renderHost, nodeDef) as any;
let componentView: ViewData = undefined !;
if (nodeDef.flags & NodeFlags.ComponentView) {
const compViewDef = resolveViewDefinition(nodeDef.element !.componentView !);
const compViewDef = resolveDefinition(nodeDef.element !.componentView !);
const rendererType = nodeDef.element !.componentRendererType;
let compRenderer: Renderer2;
if (!rendererType) {

View File

@ -11,7 +11,7 @@ import {Console} from '@angular/core/src/console';
import {ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {NgModuleInjector} from '../../src/linker/ng_module_factory';
import {InternalNgModuleRef} from '../../src/linker/ng_module_factory';
import {clearModulesForTest} from '../../src/linker/ng_module_factory_loader';
import {stringify} from '../../src/util';
@ -404,9 +404,9 @@ function declareTests({useJit}: {useJit: boolean}) {
class SomeModule {
}
const ngModule = <NgModuleInjector<any>>createModule(SomeModule);
expect(ngModule.bootstrapFactories.length).toBe(1);
expect(ngModule.bootstrapFactories[0].componentType).toBe(SomeComp);
const ngModule = <InternalNgModuleRef<any>>createModule(SomeModule);
expect(ngModule._bootstrapComponents.length).toBe(1);
expect(ngModule._bootstrapComponents[0]).toBe(SomeComp);
});
});
@ -787,6 +787,16 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(child.get(Injector)).toBe(child);
});
it('should allow to inject lazy providers via Injector.get from an eager provider that is declared earlier',
() => {
@NgModule({providers: [{provide: 'a', useFactory: () => 'aValue'}]})
class SomeModule {
public a: string;
constructor(injector: Injector) { this.a = injector.get('a'); }
}
expect(createModule(SomeModule).instance.a).toBe('aValue');
});
it('should throw when no provider defined', () => {
const injector = createInjector([]);
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');

View File

@ -342,6 +342,19 @@ export function main() {
expect(created).toBe(true);
});
it('should allow injecting lazy providers via Injector.get from an eager provider that is declared earlier',
() => {
@Component({providers: [{provide: 'a', useFactory: () => 'aValue'}], template: ''})
class SomeComponent {
public a: string;
constructor(injector: Injector) { this.a = injector.get('a'); }
}
const comp = TestBed.configureTestingModule({declarations: [SomeComponent]})
.createComponent(SomeComponent);
expect(comp.componentInstance.a).toBe('aValue');
});
it('should support ngOnDestroy for lazy providers', () => {
let created = false;
let destroyed = false;

View File

@ -100,12 +100,12 @@ describe('RouterPreloader', () => {
const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig !;
const module: any = loadedConfig.module;
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
expect(module.parent).toBe(testModule);
expect(module._parent).toBe(testModule);
const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig !;
const module2: any = loadedConfig2.module;
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
expect(module2.parent).toBe(module);
expect(module2._parent).toBe(module);
expect(events.map(e => e.toString())).toEqual([
'RouteConfigLoadStart(path: lazy)',
@ -167,12 +167,12 @@ describe('RouterPreloader', () => {
const loadedConfig: LoadedRouterConfig = c[0]._loadedConfig !;
const module: any = loadedConfig.module;
expect(module.parent).toBe(testModule);
expect(module._parent).toBe(testModule);
const loadedConfig2: LoadedRouterConfig = loadedConfig.routes[0]._loadedConfig !;
const loadedConfig3: LoadedRouterConfig = loadedConfig2.routes[0]._loadedConfig !;
const module3: any = loadedConfig3.module;
expect(module3.parent).toBe(module2);
expect(module3._parent).toBe(module2);
})));
});

View File

@ -612,12 +612,9 @@ export declare type NgIterable<T> = Array<T> | Iterable<T>;
export declare const NgModule: NgModuleDecorator;
/** @experimental */
export declare class NgModuleFactory<T> {
readonly moduleType: Type<T>;
constructor(_injectorClass: {
new (parentInjector: Injector): NgModuleInjector<T>;
}, _moduleType: Type<T>);
create(parentInjector: Injector | null): NgModuleRef<T>;
export declare abstract class NgModuleFactory<T> {
readonly abstract moduleType: Type<T>;
abstract create(parentInjector: Injector | null): NgModuleRef<T>;
}
/** @stable */