feat(compiler): integrate compiler with view engine - change detection tests work (#14412)

Included refactoring:
- make ViewData.parentIndex point to component provider index
- split NodeType.Provider into Provider / Directive / Pipe
- make purePipe take the real pipe as argument to detect changes
- order change detection:
  1) directive props
  2) renderer props

Part of #14013

PR Close #14412
This commit is contained in:
Tobias Bosch 2017-02-09 14:59:57 -08:00 committed by Miško Hevery
parent 1dc9be4b7d
commit e4e9dbe33d
39 changed files with 942 additions and 768 deletions

View File

@ -205,14 +205,14 @@ export class AotCompiler {
const pipes = ngModule.transitiveModule.pipes.map( const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference)); pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse( const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas, compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
identifierName(compMeta.type)); identifierName(compMeta.type));
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
const compiledAnimations = const compiledAnimations =
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations); this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
const viewResult = this._viewCompiler.compileComponent( const viewResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations); compMeta, parsedTemplate, stylesExpr, usedPipes, compiledAnimations);
if (componentStyles) { if (componentStyles) {
targetStatements.push( targetStatements.push(
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix)); ..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));

View File

@ -17,58 +17,9 @@ import {createPureProxy} from './identifier_util';
const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`); const VAL_UNWRAPPER_VAR = o.variable(`valUnwrapper`);
export interface NameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression;
}
export class EventHandlerVars { static event = o.variable('$event'); } export class EventHandlerVars { static event = o.variable('$event'); }
export class ConvertPropertyBindingResult { export interface LocalResolver { getLocal(name: string): o.Expression; }
constructor(
public stmts: o.Statement[], public currValExpr: o.Expression,
public forceUpdate: o.Expression) {}
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression is
* used in a property binding.
*/
export function convertPropertyBinding(
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression,
expression: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
if (!nameResolver) {
nameResolver = new DefaultNameResolver();
}
const visitor = new _AstToIrVisitor(
builder, nameResolver, implicitReceiver, VAL_UNWRAPPER_VAR, bindingId, false);
const outputExpr: o.Expression = expression.visit(visitor, _Mode.Expression);
if (!outputExpr) {
// e.g. an empty expression was given
return null;
}
if (visitor.temporaryCount) {
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
}
if (visitor.needsValueUnwrapper) {
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.push(initValueUnwrapperStmt);
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
if (visitor.needsValueUnwrapper) {
return new ConvertPropertyBindingResult(
stmts, currValExpr, VAL_UNWRAPPER_VAR.prop('hasWrappedValue'));
} else {
return new ConvertPropertyBindingResult(stmts, currValExpr, null);
}
}
export class ConvertActionBindingResult { export class ConvertActionBindingResult {
constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {} constructor(public stmts: o.Statement[], public allowDefault: o.ReadVarExpr) {}
@ -79,15 +30,31 @@ export class ConvertActionBindingResult {
* used in an action binding (e.g. an event handler). * used in an action binding (e.g. an event handler).
*/ */
export function convertActionBinding( export function convertActionBinding(
builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression, localResolver: LocalResolver, implicitReceiver: o.Expression, action: cdAst.AST,
action: cdAst.AST, bindingId: string): ConvertActionBindingResult { bindingId: string): ConvertActionBindingResult {
if (!nameResolver) { if (!localResolver) {
nameResolver = new DefaultNameResolver(); localResolver = new DefaultLocalResolver();
} }
const visitor = const actionWithoutBuiltins = convertPropertyBindingBuiltins(
new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true); {
createLiteralArrayConverter: (argCount: number) => {
// Note: no caching for literal arrays in actions.
return (args: o.Expression[]) => o.literalArr(args);
},
createLiteralMapConverter: (keys: string[]) => {
// Note: no caching for literal maps in actions.
return (args: o.Expression[]) =>
o.literalMap(<[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
},
createPipeConverter: (name: string) => {
throw new Error(`Illegal State: Actions are not allowed to contain pipes. Pipe: ${name}`);
}
},
action);
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const actionStmts: o.Statement[] = []; const actionStmts: o.Statement[] = [];
flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts); flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts);
prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts); prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts);
const lastIndex = actionStmts.length - 1; const lastIndex = actionStmts.length - 1;
let preventDefaultVar: o.ReadVarExpr = null; let preventDefaultVar: o.ReadVarExpr = null;
@ -106,11 +73,105 @@ export function convertActionBinding(
return new ConvertActionBindingResult(actionStmts, preventDefaultVar); return new ConvertActionBindingResult(actionStmts, preventDefaultVar);
} }
export interface BuiltinConverter { (args: o.Expression[]): o.Expression; }
export interface BuiltinConverterFactory {
createLiteralArrayConverter(argCount: number): BuiltinConverter;
createLiteralMapConverter(keys: string[]): BuiltinConverter;
createPipeConverter(name: string, argCount: number): BuiltinConverter;
}
export function convertPropertyBindingBuiltins(
converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
return convertBuiltins(converterFactory, ast);
}
export class ConvertPropertyBindingResult {
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression
* is used in property binding. The expression has to be preprocessed via
* `convertPropertyBindingBuiltins`.
*/
export function convertPropertyBinding(
localResolver: LocalResolver, implicitReceiver: o.Expression,
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
if (!localResolver) {
localResolver = new DefaultLocalResolver();
}
const currValExpr = createCurrValueExpr(bindingId);
const stmts: o.Statement[] = [];
const visitor = new _AstToIrVisitor(localResolver, implicitReceiver, bindingId);
const outputExpr: o.Expression = expressionWithoutBuiltins.visit(visitor, _Mode.Expression);
if (visitor.temporaryCount) {
for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i));
}
}
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr);
}
export class LegacyConvertPropertyBindingResult implements ConvertPropertyBindingResult {
constructor(
public stmts: o.Statement[], public currValExpr: o.Expression,
public forceUpdate: o.Expression) {}
}
export interface LegacyNameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getLocal(name: string): o.Expression;
}
/**
* Converts the given expression AST into an executable output AST, assuming the expression is
* used in a property binding.
*/
export function legacyConvertPropertyBinding(
builder: ClassBuilder, nameResolver: LegacyNameResolver, implicitReceiver: o.Expression,
expression: cdAst.AST, bindingId: string): LegacyConvertPropertyBindingResult {
if (!nameResolver) {
nameResolver = new LegacyDefaultNameResolver();
}
let needsValueUnwrapper = false;
const expressionWithoutBuiltins = convertBuiltins(
{
createLiteralArrayConverter: (argCount: number) => {
return (args: o.Expression[]) => legacyCreateCachedLiteralArray(builder, args);
},
createLiteralMapConverter: (keys: string[]) => {
return (args: o.Expression[]) => legacyCreateCachedLiteralMap(
builder, <[string, o.Expression][]>keys.map((key, i) => [key, args[i]]));
},
createPipeConverter: (name: string) => {
needsValueUnwrapper = true;
return (args: o.Expression[]) => VAL_UNWRAPPER_VAR.callMethod(
'unwrap', [nameResolver.callPipe(name, args[0], args.slice(1))]);
}
},
expression);
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, implicitReceiver, expressionWithoutBuiltins, bindingId);
let forceUpdate: o.Expression = null;
if (needsValueUnwrapper) {
const initValueUnwrapperStmt = VAL_UNWRAPPER_VAR.callMethod('reset', []).toStmt();
stmts.unshift(initValueUnwrapperStmt);
forceUpdate = VAL_UNWRAPPER_VAR.prop('hasWrappedValue');
}
return new LegacyConvertPropertyBindingResult(stmts, currValExpr, forceUpdate);
}
/** /**
* Creates variables that are shared by multiple calls to `convertActionBinding` / * Creates variables that are shared by multiple calls to `convertActionBinding` /
* `convertPropertyBinding` * `convertPropertyBinding`
*/ */
export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] { export function legacyCreateSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] {
const unwrapperStmts: o.Statement[] = []; const unwrapperStmts: o.Statement[] = [];
const readVars = o.findReadVarNames(stmts); const readVars = o.findReadVarNames(stmts);
if (readVars.has(VAL_UNWRAPPER_VAR.name)) { if (readVars.has(VAL_UNWRAPPER_VAR.name)) {
@ -122,6 +183,11 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St
return unwrapperStmts; return unwrapperStmts;
} }
function convertBuiltins(converterFactory: BuiltinConverterFactory, ast: cdAst.AST): cdAst.AST {
const visitor = new _BuiltinAstConverter(converterFactory);
return ast.visit(visitor);
}
function temporaryName(bindingId: string, temporaryNumber: number): string { function temporaryName(bindingId: string, temporaryNumber: number): string {
return `tmp_${bindingId}_${temporaryNumber}`; return `tmp_${bindingId}_${temporaryNumber}`;
} }
@ -162,17 +228,34 @@ function convertToStatementIfNeeded(mode: _Mode, expr: o.Expression): o.Expressi
} }
} }
class _BuiltinAstConverter extends cdAst.AstTransformer {
constructor(private _converterFactory: BuiltinConverterFactory) { super(); }
visitPipe(ast: cdAst.BindingPipe, context: any): any {
const args = [ast.exp, ...ast.args].map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createPipeConverter(ast.name, args.length));
}
visitLiteralArray(ast: cdAst.LiteralArray, context: any): any {
const args = ast.expressions.map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createLiteralArrayConverter(ast.expressions.length));
}
visitLiteralMap(ast: cdAst.LiteralMap, context: any): any {
const args = ast.values.map(ast => ast.visit(this, context));
return new BuiltinFunctionCall(
ast.span, args, this._converterFactory.createLiteralMapConverter(ast.keys));
}
}
class _AstToIrVisitor implements cdAst.AstVisitor { class _AstToIrVisitor implements cdAst.AstVisitor {
private _nodeMap = new Map<cdAst.AST, cdAst.AST>(); private _nodeMap = new Map<cdAst.AST, cdAst.AST>();
private _resultMap = new Map<cdAst.AST, o.Expression>(); private _resultMap = new Map<cdAst.AST, o.Expression>();
private _currentTemporary: number = 0; private _currentTemporary: number = 0;
public needsValueUnwrapper: boolean = false;
public temporaryCount: number = 0; public temporaryCount: number = 0;
constructor( constructor(
private _builder: ClassBuilder, private _nameResolver: NameResolver, private _localResolver: LocalResolver, private _implicitReceiver: o.Expression,
private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr, private bindingId: string) {}
private bindingId: string, private isAction: boolean) {}
visitBinary(ast: cdAst.Binary, mode: _Mode): any { visitBinary(ast: cdAst.Binary, mode: _Mode): any {
let op: o.BinaryOperator; let op: o.BinaryOperator;
@ -246,20 +329,19 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
const input = this.visit(ast.exp, _Mode.Expression); throw new Error(
const args = this.visitAll(ast.args, _Mode.Expression); `Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`);
const value = this._nameResolver.callPipe(ast.name, input, args);
if (!value) {
throw new Error(`Illegal state: Pipe ${ast.name} is not allowed here!`);
}
this.needsValueUnwrapper = true;
return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
} }
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
return convertToStatementIfNeeded( const convertedArgs = this.visitAll(ast.args, _Mode.Expression);
mode, let fnResult: o.Expression;
this.visit(ast.target, _Mode.Expression).callFn(this.visitAll(ast.args, _Mode.Expression))); if (ast instanceof BuiltinFunctionCall) {
fnResult = ast.converter(convertedArgs);
} else {
fnResult = this.visit(ast.target, _Mode.Expression).callFn(convertedArgs);
}
return convertToStatementIfNeeded(mode, fnResult);
} }
visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any { visitImplicitReceiver(ast: cdAst.ImplicitReceiver, mode: _Mode): any {
@ -301,32 +383,18 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any {
const parts = this.visitAll(ast.expressions, mode); throw new Error(`Illegal State: literal arrays should have been converted into functions`);
const literalArr =
this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts);
return convertToStatementIfNeeded(mode, literalArr);
} }
visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any {
const parts: any[] = []; throw new Error(`Illegal State: literal maps should have been converted into functions`);
for (let i = 0; i < ast.keys.length; i++) {
parts.push([ast.keys[i], this.visit(ast.values[i], _Mode.Expression)]);
}
const literalMap =
this.isAction ? o.literalMap(parts) : createCachedLiteralMap(this._builder, parts);
return convertToStatementIfNeeded(mode, literalMap);
} }
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value)); return convertToStatementIfNeeded(mode, o.literal(ast.value));
} }
private _getLocal(name: string): o.Expression { private _getLocal(name: string): o.Expression { return this._localResolver.getLocal(name); }
if (this.isAction && name == EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return this._nameResolver.getLocal(name);
}
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
const leftMostSafe = this.leftMostSafeNode(ast); const leftMostSafe = this.leftMostSafeNode(ast);
@ -581,7 +649,8 @@ function flattenStatements(arg: any, output: o.Statement[]) {
} }
} }
function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]): o.Expression { function legacyCreateCachedLiteralArray(
builder: ClassBuilder, values: o.Expression[]): o.Expression {
if (values.length === 0) { if (values.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY)); return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
} }
@ -601,7 +670,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[])
return proxyExpr.callFn(values); return proxyExpr.callFn(values);
} }
function createCachedLiteralMap( function legacyCreateCachedLiteralMap(
builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression { builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression {
if (entries.length === 0) { if (entries.length === 0) {
return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP)); return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
@ -624,10 +693,23 @@ function createCachedLiteralMap(
return proxyExpr.callFn(values); return proxyExpr.callFn(values);
} }
class DefaultLocalResolver implements LocalResolver {
getLocal(name: string): o.Expression {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return null;
}
}
class DefaultNameResolver implements NameResolver { class LegacyDefaultNameResolver implements LegacyNameResolver {
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; } callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { return null; }
getLocal(name: string): o.Expression { return null; } getLocal(name: string): o.Expression {
if (name === EventHandlerVars.event.name) {
return EventHandlerVars.event;
}
return null;
}
} }
function createCurrValueExpr(bindingId: string): o.ReadVarExpr { function createCurrValueExpr(bindingId: string): o.ReadVarExpr {
@ -646,3 +728,9 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression {
} }
return null; return null;
} }
class BuiltinFunctionCall extends cdAst.FunctionCall {
constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) {
super(span, null, args);
}
}

View File

@ -13,12 +13,12 @@ import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast'; import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {isFirstViewCheck} from './binding_util'; import {isFirstViewCheck} from './binding_util';
import {ConvertPropertyBindingResult} from './expression_converter'; import {LegacyConvertPropertyBindingResult} from './expression_converter';
import {createEnumExpression} from './identifier_util'; import {createEnumExpression} from './identifier_util';
export function createCheckRenderBindingStmt( export function createCheckRenderBindingStmt(
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst, view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult, oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult,
securityContextExpression?: o.Expression): o.Statement[] { securityContextExpression?: o.Expression): o.Statement[] {
const checkStmts: o.Statement[] = [...evalResult.stmts]; const checkStmts: o.Statement[] = [...evalResult.stmts];
const securityContext = calcSecurityContext(boundProp, securityContextExpression); const securityContext = calcSecurityContext(boundProp, securityContextExpression);
@ -84,7 +84,7 @@ function calcSecurityContext(
export function createCheckAnimationBindingStmts( export function createCheckAnimationBindingStmts(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst, view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression, boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) { oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult) {
const detachStmts: o.Statement[] = []; const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = []; const updateStmts: o.Statement[] = [];

View File

@ -8,7 +8,7 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util'; import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter'; import {EventHandlerVars, convertActionBinding, legacyConvertPropertyBinding} from './compiler_util/expression_converter';
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util'; import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {Parser} from './expression_parser/parser'; import {Parser} from './expression_parser/parser';
@ -253,7 +253,7 @@ function addCheckHostMethod(
]; ];
hostProps.forEach((hostProp, hostPropIdx) => { hostProps.forEach((hostProp, hostPropIdx) => {
const field = createCheckBindingField(builder); const field = createCheckBindingField(builder);
const evalResult = convertPropertyBinding( const evalResult = legacyConvertPropertyBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId); builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId);
if (!evalResult) { if (!evalResult) {
return; return;
@ -285,8 +285,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive
const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)]; const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)];
hostListeners.forEach((hostListener, eventIdx) => { hostListeners.forEach((hostListener, eventIdx) => {
const evalResult = convertActionBinding( const evalResult = convertActionBinding(
builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, `sub_${eventIdx}`);
`sub_${eventIdx}`);
const trueStmts = evalResult.stmts; const trueStmts = evalResult.stmts;
if (evalResult.allowDefault) { if (evalResult.allowDefault) {
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());

View File

@ -400,11 +400,36 @@ export class Identifiers {
moduleUrl: VIEW_ENGINE_MODULE_URL, moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.queryDef runtime: viewEngine.queryDef
}; };
static pureArrayDef: IdentifierSpec = {
name: 'pureArrayDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pureArrayDef
};
static pureObjectDef: IdentifierSpec = {
name: 'pureObjectDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pureObjectDef
};
static purePipeDef: IdentifierSpec = {
name: 'purePipeDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.purePipeDef
};
static pipeDef: IdentifierSpec = {
name: 'pipeDef',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.pipeDef
};
static nodeValue: IdentifierSpec = { static nodeValue: IdentifierSpec = {
name: 'nodeValue', name: 'nodeValue',
moduleUrl: VIEW_ENGINE_MODULE_URL, moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.nodeValue runtime: viewEngine.nodeValue
}; };
static unwrapValue: IdentifierSpec = {
name: 'unwrapValue',
moduleUrl: VIEW_ENGINE_MODULE_URL,
runtime: viewEngine.unwrapValue
};
} }
export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string { export function assetUrl(pkg: string, path: string = null, type: string = 'src'): string {

View File

@ -278,14 +278,14 @@ export class JitCompiler implements Compiler {
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference)); template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map( const pipes = template.ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference)); pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const parsedTemplate = this._templateParser.parse( const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas, compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
identifierName(compMeta.type)); identifierName(compMeta.type));
const compiledAnimations = const compiledAnimations =
this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations); this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations);
const compileResult = this._viewCompiler.compileComponent( const compileResult = this._viewCompiler.compileComponent(
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar), compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
pipes, compiledAnimations); usedPipes, compiledAnimations);
const statements = stylesCompileResult.componentStylesheet.statements const statements = stylesCompileResult.componentStylesheet.statements
.concat(...compiledAnimations.map(ca => ca.statements)) .concat(...compiledAnimations.map(ca => ca.statements))
.concat(compileResult.statements); .concat(compileResult.statements);

View File

@ -51,6 +51,7 @@ export class BoundProperty {
*/ */
export class BindingParser { export class BindingParser {
pipesByName: Map<string, CompilePipeSummary> = new Map(); pipesByName: Map<string, CompilePipeSummary> = new Map();
private _usedPipes: Map<string, CompilePipeSummary> = new Map();
constructor( constructor(
private _exprParser: Parser, private _interpolationConfig: InterpolationConfig, private _exprParser: Parser, private _interpolationConfig: InterpolationConfig,
@ -59,6 +60,8 @@ export class BindingParser {
pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe));
} }
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan): createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundElementPropertyAst[] { BoundElementPropertyAst[] {
if (dirMeta.hostProperties) { if (dirMeta.hostProperties) {
@ -377,11 +380,14 @@ export class BindingParser {
const collector = new PipeCollector(); const collector = new PipeCollector();
ast.visit(collector); ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => { collector.pipes.forEach((ast, pipeName) => {
if (!this.pipesByName.has(pipeName)) { const pipeMeta = this.pipesByName.get(pipeName);
if (!pipeMeta) {
this._reportError( this._reportError(
`The pipe '${pipeName}' could not be found`, `The pipe '${pipeName}' could not be found`,
new ParseSourceSpan( new ParseSourceSpan(
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end))); sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
} else {
this._usedPipes.set(pipeName, pipeMeta);
} }
}); });
} }

View File

@ -7,7 +7,9 @@
*/ */
import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core'; import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {I18NHtmlParser} from '../i18n/i18n_html_parser'; import {I18NHtmlParser} from '../i18n/i18n_html_parser';
@ -25,10 +27,12 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
import {CssSelector, SelectorMatcher} from '../selector'; import {CssSelector, SelectorMatcher} from '../selector';
import {isStyleUrlResolvable} from '../style_url_resolver'; import {isStyleUrlResolvable} from '../style_url_resolver';
import {syntaxError} from '../util'; import {syntaxError} from '../util';
import {BindingParser, BoundProperty} from './binding_parser'; import {BindingParser, BoundProperty} from './binding_parser';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
import {PreparsedElementType, preparseElement} from './template_preparser'; import {PreparsedElementType, preparseElement} from './template_preparser';
// Group 1 = "bind-" // Group 1 = "bind-"
// Group 2 = "let-" // Group 2 = "let-"
// Group 3 = "ref-/#" // Group 3 = "ref-/#"
@ -76,7 +80,9 @@ export class TemplateParseError extends ParseError {
} }
export class TemplateParseResult { export class TemplateParseResult {
constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {} constructor(
public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[],
public errors?: ParseError[]) {}
} }
@CompilerInjectable() @CompilerInjectable()
@ -88,7 +94,8 @@ export class TemplateParser {
parse( parse(
component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[], component: CompileDirectiveMetadata, template: string, directives: CompileDirectiveSummary[],
pipes: CompilePipeSummary[], schemas: SchemaMetadata[], templateUrl: string): TemplateAst[] { pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl); const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING); const warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING);
const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL); const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL);
@ -102,7 +109,7 @@ export class TemplateParser {
throw syntaxError(`Template parse errors:\n${errorString}`); throw syntaxError(`Template parse errors:\n${errorString}`);
} }
return result.templateAst; return {template: result.templateAst, pipes: result.usedPipes};
} }
tryParse( tryParse(
@ -121,6 +128,7 @@ export class TemplateParser {
templateUrl: string): TemplateParseResult { templateUrl: string): TemplateParseResult {
let result: TemplateAst[]; let result: TemplateAst[];
const errors = htmlAstWithErrors.errors; const errors = htmlAstWithErrors.errors;
const usedPipes: CompilePipeSummary[] = [];
if (htmlAstWithErrors.rootNodes.length > 0) { if (htmlAstWithErrors.rootNodes.length > 0) {
const uniqDirectives = removeSummaryDuplicates(directives); const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeSummaryDuplicates(pipes); const uniqPipes = removeSummaryDuplicates(pipes);
@ -140,13 +148,14 @@ export class TemplateParser {
errors); errors);
result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT);
errors.push(...providerViewContext.errors); errors.push(...providerViewContext.errors);
usedPipes.push(...bindingParser.getUsedPipes());
} else { } else {
result = []; result = [];
} }
this._assertNoReferenceDuplicationOnTemplate(result, errors); this._assertNoReferenceDuplicationOnTemplate(result, errors);
if (errors.length > 0) { if (errors.length > 0) {
return new TemplateParseResult(result, errors); return new TemplateParseResult(result, usedPipes, errors);
} }
if (this.transforms) { if (this.transforms) {
@ -154,7 +163,7 @@ export class TemplateParser {
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); }); (transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
} }
return new TemplateParseResult(result, errors); return new TemplateParseResult(result, usedPipes, errors);
} }
expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult { expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult {
@ -303,11 +312,12 @@ class TemplateParseVisitor implements html.Visitor {
const {directives: directiveMetas, matchElement} = const {directives: directiveMetas, matchElement} =
this._parseDirectives(this.selectorMatcher, elementCssSelector); this._parseDirectives(this.selectorMatcher, elementCssSelector);
const references: ReferenceAst[] = []; const references: ReferenceAst[] = [];
const boundDirectivePropNames = new Set<string>();
const directiveAsts = this._createDirectiveAsts( const directiveAsts = this._createDirectiveAsts(
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps, isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references); elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames);
const elementProps: BoundElementPropertyAst[] = const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts); element.name, elementOrDirectiveProps, boundDirectivePropNames);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates; const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext( const providerContext = new ProviderElementContext(
@ -372,11 +382,12 @@ class TemplateParseVisitor implements html.Visitor {
createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs);
const {directives: templateDirectiveMetas} = const {directives: templateDirectiveMetas} =
this._parseDirectives(this.selectorMatcher, templateCssSelector); this._parseDirectives(this.selectorMatcher, templateCssSelector);
const templateBoundDirectivePropNames = new Set<string>();
const templateDirectiveAsts = this._createDirectiveAsts( const templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [], true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, []); element.sourceSpan, [], templateBoundDirectivePropNames);
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts( const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateDirectiveAsts); element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
this._assertNoComponentsNorElementBindingsOnTemplate( this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan); templateDirectiveAsts, templateElementProps, element.sourceSpan);
const templateProviderContext = new ProviderElementContext( const templateProviderContext = new ProviderElementContext(
@ -544,7 +555,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectiveAsts( private _createDirectiveAsts(
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[], isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[], props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
const matchedReferences = new Set<string>(); const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null; let component: CompileDirectiveSummary = null;
@ -557,13 +569,14 @@ class TemplateParseVisitor implements html.Visitor {
component = directive; component = directive;
} }
const directiveProperties: BoundDirectivePropertyAst[] = []; const directiveProperties: BoundDirectivePropertyAst[] = [];
const hostProperties = let hostProperties =
this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan); this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan);
// Note: We need to check the host properties here as well, // Note: We need to check the host properties here as well,
// as we don't know the element name in the DirectiveWrapperCompiler yet. // as we don't know the element name in the DirectiveWrapperCompiler yet.
this._checkPropertiesInSchema(elementName, hostProperties); hostProperties = this._checkPropertiesInSchema(elementName, hostProperties);
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan); const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan);
this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties); this._createDirectivePropertyAsts(
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
elementOrDirectiveRefs.forEach((elOrDirRef) => { elementOrDirectiveRefs.forEach((elOrDirRef) => {
if ((elOrDirRef.value.length === 0 && directive.isComponent) || if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
(directive.exportAs == elOrDirRef.value)) { (directive.exportAs == elOrDirRef.value)) {
@ -596,7 +609,8 @@ class TemplateParseVisitor implements html.Visitor {
private _createDirectivePropertyAsts( private _createDirectivePropertyAsts(
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[], directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { targetBoundDirectiveProps: BoundDirectivePropertyAst[],
targetBoundDirectivePropNames: Set<string>) {
if (directiveProperties) { if (directiveProperties) {
const boundPropsByName = new Map<string, BoundProperty>(); const boundPropsByName = new Map<string, BoundProperty>();
boundProps.forEach(boundProp => { boundProps.forEach(boundProp => {
@ -613,32 +627,27 @@ class TemplateParseVisitor implements html.Visitor {
// Bindings are optional, so this binding only needs to be set up if an expression is given. // Bindings are optional, so this binding only needs to be set up if an expression is given.
if (boundProp) { if (boundProp) {
targetBoundDirectivePropNames.add(boundProp.name);
if (!isEmptyExpression(boundProp.expression)) {
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst( targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan)); dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
} }
}
}); });
} }
} }
private _createElementPropertyAsts( private _createElementPropertyAsts(
elementName: string, props: BoundProperty[], elementName: string, props: BoundProperty[],
directives: DirectiveAst[]): BoundElementPropertyAst[] { boundDirectivePropNames: Set<string>): BoundElementPropertyAst[] {
const boundElementProps: BoundElementPropertyAst[] = []; const boundElementProps: BoundElementPropertyAst[] = [];
const boundDirectivePropsIndex = new Map<string, BoundDirectivePropertyAst>();
directives.forEach((directive: DirectiveAst) => {
directive.inputs.forEach((prop: BoundDirectivePropertyAst) => {
boundDirectivePropsIndex.set(prop.templateName, prop);
});
});
props.forEach((prop: BoundProperty) => { props.forEach((prop: BoundProperty) => {
if (!prop.isLiteral && !boundDirectivePropsIndex.get(prop.name)) { if (!prop.isLiteral && !boundDirectivePropNames.has(prop.name)) {
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop)); boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
} }
}); });
this._checkPropertiesInSchema(elementName, boundElementProps); return this._checkPropertiesInSchema(elementName, boundElementProps);
return boundElementProps;
} }
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] { private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
@ -723,8 +732,11 @@ class TemplateParseVisitor implements html.Visitor {
}); });
} }
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) { private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]):
boundProps.forEach((boundProp) => { BoundElementPropertyAst[] {
// Note: We can't filter out empty expressions before this method,
// as we still want to validate them!
return boundProps.filter((boundProp) => {
if (boundProp.type === PropertyBindingType.Property && if (boundProp.type === PropertyBindingType.Property &&
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) { !this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
let errorMsg = let errorMsg =
@ -741,6 +753,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
this._reportError(errorMsg, boundProp.sourceSpan); this._reportError(errorMsg, boundProp.sourceSpan);
} }
return !isEmptyExpression(boundProp.value);
}); });
} }
@ -870,3 +883,10 @@ export function removeSummaryDuplicates<T extends{type: CompileTypeMetadata}>(it
return Array.from(map.values()); return Array.from(map.values());
} }
function isEmptyExpression(ast: AST): boolean {
if (ast instanceof ASTWithSource) {
ast = ast.ast;
}
return ast instanceof EmptyExpr;
}

View File

@ -8,7 +8,7 @@
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata';
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter'; import {EventHandlerVars, LegacyNameResolver} from '../compiler_util/expression_converter';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -33,7 +33,7 @@ export class CompileViewRootNode {
public ngContentIndex?: number) {} public ngContentIndex?: number) {}
} }
export class CompileView implements NameResolver { export class CompileView implements LegacyNameResolver {
public viewType: ViewType; public viewType: ViewType;
public viewQueries: Map<any, CompileQuery[]>; public viewQueries: Map<any, CompileQuery[]>;

View File

@ -102,8 +102,8 @@ function generateHandleEventMethod(
}); });
boundEvents.forEach((renderEvent, renderEventIdx) => { boundEvents.forEach((renderEvent, renderEventIdx) => {
const evalResult = convertActionBinding( const evalResult = convertActionBinding(
compileElement.view, compileElement.view, compileElement.view.componentContext, compileElement.view, compileElement.view.componentContext, renderEvent.handler,
renderEvent.handler, `sub_${renderEventIdx}`); `sub_${renderEventIdx}`);
const trueStmts = evalResult.stmts; const trueStmts = evalResult.stmts;
if (evalResult.allowDefault) { if (evalResult.allowDefault) {
trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt());

View File

@ -9,7 +9,7 @@
import {SecurityContext} from '@angular/core'; import {SecurityContext} from '@angular/core';
import {createCheckBindingField} from '../compiler_util/binding_util'; import {createCheckBindingField} from '../compiler_util/binding_util';
import {convertPropertyBinding} from '../compiler_util/expression_converter'; import {legacyConvertPropertyBinding} from '../compiler_util/expression_converter';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util'; import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util';
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
@ -26,7 +26,7 @@ import {getHandleEventMethodName} from './util';
export function bindRenderText( export function bindRenderText(
boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void { boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void {
const valueField = createCheckBindingField(view); const valueField = createCheckBindingField(view);
const evalResult = convertPropertyBinding( const evalResult = legacyConvertPropertyBinding(
view, view, view.componentContext, boundText.value, valueField.bindingId); view, view, view.componentContext, boundText.value, valueField.bindingId);
if (!evalResult) { if (!evalResult) {
return null; return null;
@ -53,7 +53,7 @@ export function bindRenderInputs(
boundProps.forEach((boundProp) => { boundProps.forEach((boundProp) => {
const bindingField = createCheckBindingField(view); const bindingField = createCheckBindingField(view);
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp);
const evalResult = convertPropertyBinding( const evalResult = legacyConvertPropertyBinding(
view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId); view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId);
if (!evalResult) { if (!evalResult) {
return; return;
@ -123,7 +123,7 @@ export function bindDirectiveInputs(
const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`; const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`;
detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input);
const evalResult = const evalResult =
convertPropertyBinding(view, view, view.componentContext, input.value, bindingId); legacyConvertPropertyBinding(view, view, view.componentContext, input.value, bindingId);
if (!evalResult) { if (!evalResult) {
return; return;
} }

View File

@ -9,7 +9,7 @@
import {ViewEncapsulation} from '@angular/core'; import {ViewEncapsulation} from '@angular/core';
import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter'; import {legacyCreateSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util'; import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier, identifierToken} from '../identifiers'; import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
@ -586,7 +586,7 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts)); stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts));
} }
const varStmts = createSharedBindingVariablesIfNeeded(stmts); const varStmts = legacyCreateSharedBindingVariablesIfNeeded(stmts);
return varStmts.concat(stmts); return varStmts.concat(stmts);
} }

View File

@ -10,7 +10,7 @@ import {ChangeDetectionStrategy} from '@angular/core';
import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {AnimationEntryCompileResult} from '../animation/animation_compiler';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenReference} from '../compile_metadata';
import {EventHandlerVars, NameResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter'; import {BuiltinConverter, BuiltinConverterFactory, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers'; import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers';
@ -25,6 +25,7 @@ import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDep
const CLASS_ATTR = 'class'; const CLASS_ATTR = 'class';
const STYLE_ATTR = 'style'; const STYLE_ATTR = 'style';
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
@CompilerInjectable() @CompilerInjectable()
export class ViewCompilerNext extends ViewCompiler { export class ViewCompilerNext extends ViewCompiler {
@ -35,7 +36,7 @@ export class ViewCompilerNext extends ViewCompiler {
compileComponent( compileComponent(
component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression,
pipes: CompilePipeSummary[], usedPipes: CompilePipeSummary[],
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
const compName = identifierName(component.type) + (component.isHost ? `_Host` : ''); const compName = identifierName(component.type) + (component.isHost ? `_Host` : '');
@ -44,7 +45,7 @@ export class ViewCompilerNext extends ViewCompiler {
const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => {
const embeddedViewIndex = embeddedViewCount++; const embeddedViewIndex = embeddedViewCount++;
const viewName = `view_${compName}_${embeddedViewIndex}`; const viewName = `view_${compName}_${embeddedViewIndex}`;
return new ViewBuilder(parent, viewName, viewBuilderFactory); return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory);
}; };
const visitor = viewBuilderFactory(null); const visitor = viewBuilderFactory(null);
@ -80,20 +81,31 @@ const NODE_INDEX_VAR = o.variable('nodeIndex');
const EVENT_NAME_VAR = o.variable('eventName'); const EVENT_NAME_VAR = o.variable('eventName');
const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`); const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`);
class ViewBuilder implements TemplateAstVisitor, NameResolver { class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory {
private nodeDefs: o.Expression[] = []; private nodeDefs: o.Expression[] = [];
private purePipeNodeIndices: {[pipeName: string]: number} = {};
private refNodeIndices: {[refName: string]: number} = {}; private refNodeIndices: {[refName: string]: number} = {};
private variables: VariableAst[] = []; private variables: VariableAst[] = [];
private children: ViewBuilder[] = []; private children: ViewBuilder[] = [];
private updateExpressions: UpdateExpression[] = []; private updateDirectivesExpressions: UpdateExpression[] = [];
private updateRendererExpressions: UpdateExpression[] = [];
private handleEventExpressions: HandleEventExpression[] = []; private handleEventExpressions: HandleEventExpression[] = [];
constructor( constructor(
private parent: ViewBuilder, public viewName: string, private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[],
private viewBuilderFactory: ViewBuilderFactory) {} private viewBuilderFactory: ViewBuilderFactory) {}
visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) { visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) {
this.variables = variables; this.variables = variables;
// create the pipes for the pure pipes immediately, so that we know their indices.
if (!this.parent) {
this.usedPipes.forEach((pipe) => {
if (pipe.pure) {
this.purePipeNodeIndices[pipe.name] = this._createPipe(pipe);
}
});
}
templateVisitAll(this, astNodes, {elementDepth}); templateVisitAll(this, astNodes, {elementDepth});
if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) { if (astNodes.length === 0 || (this.parent && hasViewContainer(astNodes[astNodes.length - 1]))) {
// if the view is empty, or an embedded view has a view container as last root nde, // if the view is empty, or an embedded view has a view container as last root nde,
@ -108,46 +120,16 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const compType = o.importType(component.type); const compType = o.importType(component.type);
this.children.forEach((child) => { child.build(component, targetStatements); }); this.children.forEach((child) => { child.build(component, targetStatements); });
const updateStmts: o.Statement[] = []; const updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions, compType);
let updateBindingCount = 0; const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions, compType);
this.updateExpressions
.forEach(
({expressions, nodeIndex}) => {
const exprs = expressions.map(({context, value}) => {
const bindingId = `${updateBindingCount++}`;
const {stmts, currValExpr} =
convertPropertyBinding(null, this, context, value, bindingId);
updateStmts.push(...stmts);
return currValExpr;
});
if (exprs.length > 10) {
updateStmts.push(
CHECK_VAR
.callFn([
VIEW_VAR, o.literal(nodeIndex),
o.literal(viewEngine.ArgumentType.Dynamic), o.literalArr(exprs)
])
.toStmt());
} else {
updateStmts.push(
CHECK_VAR.callFn((<o.Expression[]>[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline)]).concat(exprs)).toStmt());
}
});
let updateFn: o.Expression;
if (updateStmts.length > 0) {
updateFn = o.fn(
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
} else {
updateFn = o.NULL_EXPR;
}
const handleEventStmts: o.Statement[] = []; const handleEventStmts: o.Statement[] = [];
let handleEventBindingCount = 0; let handleEventBindingCount = 0;
this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => { this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => {
const bindingId = `${handleEventBindingCount++}`; const bindingId = `${handleEventBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const {stmts, allowDefault} = const {stmts, allowDefault} =
convertActionBinding(null, this, context, expression, bindingId); convertActionBinding(nameResolver, context, expression, bindingId);
const trueStmts = stmts; const trueStmts = stmts;
if (allowDefault) { if (allowDefault) {
trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt()); trueStmts.push(ALLOW_DEFAULT_VAR.set(allowDefault.and(ALLOW_DEFAULT_VAR)).toStmt());
@ -181,13 +163,39 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const viewFactory = new o.DeclareFunctionStmt( const viewFactory = new o.DeclareFunctionStmt(
this.viewName, [], this.viewName, [],
[new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([ [new o.ReturnStatement(o.importExpr(createIdentifier(Identifiers.viewDef)).callFn([
o.literal(viewFlags), o.literalArr(this.nodeDefs), updateFn, handleEventFn o.literal(viewFlags), o.literalArr(this.nodeDefs), updateDirectivesFn, updateRendererFn,
handleEventFn
]))]); ]))]);
targetStatements.push(viewFactory); targetStatements.push(viewFactory);
return targetStatements; return targetStatements;
} }
private _createUpdateFn(expressions: UpdateExpression[], compType: o.Type): o.Expression {
const updateStmts: o.Statement[] = [];
let updateBindingCount = 0;
expressions.forEach(({expressions, nodeIndex}) => {
const exprs = expressions.map(({context, value}) => {
const bindingId = `${updateBindingCount++}`;
const nameResolver = context === COMP_VAR ? this : null;
const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, context, value, bindingId);
updateStmts.push(...stmts);
return currValExpr;
});
updateStmts.push(callCheckStmt(nodeIndex, exprs).toStmt());
});
let updateFn: o.Expression;
if (updateStmts.length > 0) {
updateFn = o.fn(
[new o.FnParam(CHECK_VAR.name), new o.FnParam(VIEW_VAR.name)],
[COMP_VAR.set(VIEW_VAR.prop('component')).toDeclStmt(compType), ...updateStmts]);
} else {
updateFn = o.NULL_EXPR;
}
return updateFn;
}
visitNgContent(ast: NgContentAst, context: any): any {} visitNgContent(ast: NgContentAst, context: any): any {}
visitText(ast: TextAst, context: any): any { visitText(ast: TextAst, context: any): any {
@ -199,17 +207,20 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
visitBoundText(ast: BoundTextAst, context: any): any { visitBoundText(ast: BoundTextAst, context: any): any {
const nodeIndex = this.nodeDefs.length; const nodeIndex = this.nodeDefs.length;
// reserve the space in the nodeDefs array
this.nodeDefs.push(null);
const astWithSource = <ASTWithSource>ast.value; const astWithSource = <ASTWithSource>ast.value;
const inter = <Interpolation>astWithSource.ast; const inter = <Interpolation>astWithSource.ast;
this.updateExpressions.push({
nodeIndex, this._addUpdateExpressions(
expressions: inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }) nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }),
}); this.updateRendererExpressions);
// textDef(ngContentIndex: number, constants: string[]): NodeDef; // textDef(ngContentIndex: number, constants: string[]): NodeDef;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.textDef)).callFn([ this.nodeDefs[nodeIndex] = o.importExpr(createIdentifier(Identifiers.textDef)).callFn([
o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s))) o.NULL_EXPR, o.literalArr(inter.strings.map(s => o.literal(s)))
])); ]);
} }
visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any { visitEmbeddedTemplate(ast: EmbeddedTemplateAst, context: {elementDepth: number}): any {
@ -219,11 +230,12 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context); const {flags, queryMatchesExpr} = this._visitElementOrTemplate(nodeIndex, ast, context);
const childCount = this.nodeDefs.length - nodeIndex - 1;
const childVisitor = this.viewBuilderFactory(this); const childVisitor = this.viewBuilderFactory(this);
this.children.push(childVisitor); this.children.push(childVisitor);
childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1); childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1);
const childCount = this.nodeDefs.length - nodeIndex - 1;
// anchorDef( // anchorDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef; // childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef;
@ -243,12 +255,9 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1}); templateVisitAll(this, ast.children, {elementDepth: context.elementDepth + 1});
const childCount = this.nodeDefs.length - nodeIndex - 1; ast.inputs.forEach(
(inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); });
ast.inputs.forEach((inputAst) => { this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions);
hostBindings.push({context: COMP_VAR, value: (<ASTWithSource>inputAst.value).ast});
});
this.updateExpressions.push({nodeIndex, expressions: hostBindings});
const inputDefs = elementBindingDefs(ast.inputs); const inputDefs = elementBindingDefs(ast.inputs);
ast.directives.forEach( ast.directives.forEach(
@ -258,6 +267,8 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
o.literal(eventName); o.literal(eventName);
}); });
const childCount = this.nodeDefs.length - nodeIndex - 1;
// elementDef( // elementDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
// childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
@ -355,13 +366,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
ast.outputs.forEach( ast.outputs.forEach(
(outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); }); (outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); });
hostEvents.forEach((hostEvent) => { hostEvents.forEach((hostEvent) => {
this.handleEventExpressions.push({ this._addHandleEventExpression(
nodeIndex, nodeIndex, hostEvent.context,
context: hostEvent.context,
eventName:
viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name), viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name),
expression: (<ASTWithSource>hostEvent.eventAst.handler).ast hostEvent.eventAst.handler);
});
}); });
return { return {
@ -383,6 +391,22 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
// reserve the space in the nodeDefs array so we can add children // reserve the space in the nodeDefs array so we can add children
this.nodeDefs.push(null); this.nodeDefs.push(null);
directiveAst.directive.queries.forEach((query, queryIndex) => {
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
});
// Note: the operation below might also create new nodeDefs,
// but we don't want them to be a child of a directive,
// as they might be a provider/pipe on their own.
// I.e. we only allow queries as children of directives nodes.
const childCount = this.nodeDefs.length - nodeIndex - 1;
const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} = const {flags, queryMatchExprs, providerExpr, providerType, depsExpr} =
this._visitProviderOrDirective(providerAst, queryMatches); this._visitProviderOrDirective(providerAst, queryMatches);
@ -415,11 +439,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
} }
}); });
if (directiveAst.inputs.length) { if (directiveAst.inputs.length) {
this.updateExpressions.push({ this._addUpdateExpressions(
nodeIndex, nodeIndex,
expressions: directiveAst.inputs.map( directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }),
input => { return {context: COMP_VAR, value: (<ASTWithSource>input.value).ast}; }) this.updateDirectivesExpressions);
});
} }
const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
@ -434,19 +457,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
const hostEvents = directiveAst.hostEvents.map( const hostEvents = directiveAst.hostEvents.map(
(hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; }); (hostEventAst) => { return {context: dirContextExpr, eventAst: hostEventAst}; });
const childCount = directiveAst.directive.queries.length;
directiveAst.directive.queries.forEach((query, queryIndex) => {
const queryId: QueryId = {elementDepth, directiveIndex, queryIndex};
const bindingType =
query.first ? viewEngine.QueryBindingType.First : viewEngine.QueryBindingType.All;
// queryDef(
// flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef
// {
this.nodeDefs.push(o.importExpr(createIdentifier(Identifiers.queryDef)).callFn([
o.literal(viewEngine.NodeFlags.HasContentQuery), o.literal(calcQueryId(queryId)),
new o.LiteralMapExpr([new o.LiteralMapEntry(query.propertyName, o.literal(bindingType))])
]));
});
// directiveDef( // directiveDef(
// flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: // flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor:
@ -514,10 +524,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
return {flags, queryMatchExprs, providerExpr, providerType, depsExpr}; return {flags, queryMatchExprs, providerExpr, providerType, depsExpr};
} }
callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
throw new Error('Pipes are not yet supported!');
}
getLocal(name: string): o.Expression { getLocal(name: string): o.Expression {
if (name == EventHandlerVars.event.name) { if (name == EventHandlerVars.event.name) {
return EventHandlerVars.event; return EventHandlerVars.event;
@ -536,12 +542,123 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver {
// check variables // check variables
const varAst = currBuilder.variables.find((varAst) => varAst.name === name); const varAst = currBuilder.variables.find((varAst) => varAst.name === name);
if (varAst) { if (varAst) {
return currViewExpr.prop('context').prop(varAst.value); const varValue = varAst.value || IMPLICIT_TEMPLATE_VAR;
return currViewExpr.prop('context').prop(varValue);
} }
} }
return null; return null;
} }
createLiteralArrayConverter(argCount: number): BuiltinConverter {
if (argCount === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
// pureArrayDef(argCount: number): NodeDef;
const nodeDef =
o.importExpr(createIdentifier(Identifiers.pureArrayDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createLiteralMapConverter(keys: string[]): BuiltinConverter {
if (keys.length === 0) {
const valueExpr = o.importExpr(createIdentifier(Identifiers.EMPTY_MAP));
return () => valueExpr;
}
const nodeIndex = this.nodeDefs.length;
// function pureObjectDef(propertyNames: string[]): NodeDef
const nodeDef = o.importExpr(createIdentifier(Identifiers.pureObjectDef)).callFn([o.literalArr(
keys.map(key => o.literal(key)))]);
this.nodeDefs.push(nodeDef);
return (args: o.Expression[]) => callCheckStmt(nodeIndex, args);
}
createPipeConverter(name: string, argCount: number): BuiltinConverter {
const pipe = this._findPipe(name);
if (pipe.pure) {
const nodeIndex = this.nodeDefs.length;
// function purePipeDef(argCount: number): NodeDef;
const nodeDef =
o.importExpr(createIdentifier(Identifiers.purePipeDef)).callFn([o.literal(argCount)]);
this.nodeDefs.push(nodeDef);
// find underlying pipe in the component view
let compViewExpr: o.Expression = VIEW_VAR;
let compBuilder: ViewBuilder = this;
while (compBuilder.parent) {
compBuilder = compBuilder.parent;
compViewExpr = compViewExpr.prop('parent');
}
const pipeNodeIndex = compBuilder.purePipeNodeIndices[name];
const pipeValueExpr: o.Expression =
o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
compViewExpr, o.literal(pipeNodeIndex)
]);
return (args: o.Expression[]) =>
callUnwrapValue(callCheckStmt(nodeIndex, [pipeValueExpr].concat(args)));
} else {
const nodeIndex = this._createPipe(pipe);
const nodeValueExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([
VIEW_VAR, o.literal(nodeIndex)
]);
return (args: o.Expression[]) => callUnwrapValue(nodeValueExpr.callMethod('transform', args));
}
}
private _findPipe(name: string): CompilePipeSummary {
return this.usedPipes.find((pipeSummary) => pipeSummary.name === name);
}
private _createPipe(pipe: CompilePipeSummary): number {
const nodeIndex = this.nodeDefs.length;
let flags = viewEngine.NodeFlags.None;
pipe.type.lifecycleHooks.forEach((lifecycleHook) => {
// for pipes, we only support ngOnDestroy
if (lifecycleHook === LifecycleHooks.OnDestroy) {
flags |= lifecycleHookToNodeFlag(lifecycleHook);
}
});
const depExprs = pipe.type.diDeps.map(depDef);
// function pipeDef(
// flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef
const nodeDef = o.importExpr(createIdentifier(Identifiers.pipeDef)).callFn([
o.literal(flags), o.importExpr(pipe.type), o.literalArr(depExprs)
]);
this.nodeDefs.push(nodeDef);
return nodeIndex;
}
// Attention: This might create new nodeDefs (for pipes and literal arrays and literal maps)!
private _addUpdateExpressions(
nodeIndex: number, expressions: {context: o.Expression, value: AST}[],
target: UpdateExpression[]) {
if (expressions.length === 0) {
return;
}
const transformedExpressions = expressions.map(({context, value}) => {
if (value instanceof ASTWithSource) {
value = value.ast;
}
return {context, value: convertPropertyBindingBuiltins(this, value)};
});
target.push({nodeIndex, expressions: transformedExpressions});
}
private _addHandleEventExpression(
nodeIndex: number, context: o.Expression, eventName: string, expression: AST) {
if (expression instanceof ASTWithSource) {
expression = expression.ast;
}
this.handleEventExpressions.push({nodeIndex, context, eventName, expression});
}
visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {} visitDirective(ast: DirectiveAst, context: {usedEvents: Set<string>}): any {}
visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {} visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {}
visitReference(ast: ReferenceAst, context: any): any {} visitReference(ast: ReferenceAst, context: any): any {}
@ -736,3 +853,19 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s
return attrValue2; return attrValue2;
} }
} }
function callCheckStmt(nodeIndex: number, exprs: o.Expression[]): o.Expression {
if (exprs.length > 10) {
return CHECK_VAR.callFn([
VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Dynamic),
o.literalArr(exprs)
]);
} else {
return CHECK_VAR.callFn(
[VIEW_VAR, o.literal(nodeIndex), o.literal(viewEngine.ArgumentType.Inline), ...exprs]);
}
}
function callUnwrapValue(expr: o.Expression): o.Expression {
return o.importExpr(createIdentifier(Identifiers.unwrapValue)).callFn([expr]);
}

View File

@ -70,7 +70,8 @@ export function main() {
if (pipes === null) { if (pipes === null) {
pipes = []; pipes = [];
} }
return parser.parse(component, template, directives, pipes, schemas, 'TestComp'); return parser.parse(component, template, directives, pipes, schemas, 'TestComp')
.template;
}; };
})); }));
} }
@ -306,10 +307,10 @@ export function main() {
isComponent: true, isComponent: true,
template: new CompileTemplateMetadata({interpolation: ['{%', '%}']}) template: new CompileTemplateMetadata({interpolation: ['{%', '%}']})
}); });
expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], [], 'TestComp'), { expect(humanizeTplAst(
start: '{%', parser.parse(component, '{%a%}', [], [], [], 'TestComp').template,
end: '%}' {start: '{%', end: '%}'}))
})).toEqual([[BoundTextAst, '{% a %}']]); .toEqual([[BoundTextAst, '{% a %}']]);
})); }));
describe('bound properties', () => { describe('bound properties', () => {

View File

@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
import {SecurityContext} from '../security'; import {SecurityContext} from '../security';
import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types'; import {BindingDef, BindingType, DebugContext, DisposableFn, ElementData, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, asElementData} from './types';
import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack, unwrapValue} from './util'; import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack} from './util';
export function anchorDef( export function anchorDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number,
@ -209,8 +209,6 @@ function checkAndUpdateElementValue(view: ViewData, def: NodeDef, bindingIdx: nu
if (!checkAndUpdateBinding(view, def, bindingIdx, value)) { if (!checkAndUpdateBinding(view, def, bindingIdx, value)) {
return; return;
} }
value = unwrapValue(value);
const binding = def.bindings[bindingIdx]; const binding = def.bindings[bindingIdx];
const name = binding.name; const name = binding.name;
const renderNode = asElementData(view, def.index).renderElement; const renderNode = asElementData(view, def.index).renderElement;

View File

@ -31,7 +31,6 @@ export function viewDebugError(msg: string, context: DebugContext): Error {
const err = new Error(msg); const err = new Error(msg);
(err as any)[ERROR_DEBUG_CONTEXT] = context; (err as any)[ERROR_DEBUG_CONTEXT] = context;
err.stack = context.source; err.stack = context.source;
context.view.state |= ViewState.Errored;
return err; return err;
} }

View File

@ -8,13 +8,13 @@
export {anchorDef, elementDef} from './element'; export {anchorDef, elementDef} from './element';
export {ngContentDef} from './ng_content'; export {ngContentDef} from './ng_content';
export {directiveDef, providerDef} from './provider'; export {directiveDef, pipeDef, providerDef} from './provider';
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression'; export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
export {queryDef} from './query'; export {queryDef} from './query';
export {createComponentFactory} from './refs'; export {createComponentFactory} from './refs';
export {initServicesIfNeeded} from './services'; export {initServicesIfNeeded} from './services';
export {textDef} from './text'; export {textDef} from './text';
export {elementEventFullName, nodeValue, rootRenderNodes} from './util'; export {elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util';
export {viewDef} from './view'; export {viewDef} from './view';
export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';

View File

@ -15,8 +15,8 @@ import * as v1renderer from '../render/api';
import {Type} from '../type'; import {Type} from '../type';
import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs'; import {createChangeDetectorRef, createInjector, createTemplateRef, createViewContainerRef} from './refs';
import {BindingDef, BindingType, DepDef, DepFlags, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderOutputDef, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types'; import {BindingDef, BindingType, DepDef, DepFlags, DirectiveOutputDef, DisposableFn, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderType, QueryBindingType, QueryDef, QueryValueType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewState, asElementData, asProviderData} from './types';
import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, unwrapValue, viewParentDiIndex} from './util'; import {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, viewParentElIndex} from './util';
const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const RendererV1TokenKey = tokenKey(v1renderer.Renderer);
const ElementRefTokenKey = tokenKey(ElementRef); const ElementRefTokenKey = tokenKey(ElementRef);
@ -31,45 +31,55 @@ export function directiveDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any, flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any,
deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]},
outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef { outputs?: {[name: string]: string}, component?: () => ViewDefinition): NodeDef {
return _providerDef(
flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps, props, outputs,
component);
}
export function providerDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any,
value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
return _providerDef(flags, matchedQueries, 0, type, token, value, deps);
}
export function _providerDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number,
type: ProviderType, token: any, value: any, deps: ([DepFlags, any] | any)[],
props?: {[name: string]: [number, string]}, outputs?: {[name: string]: string},
component?: () => ViewDefinition): NodeDef {
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
const bindings: BindingDef[] = []; const bindings: BindingDef[] = [];
if (props) { if (props) {
for (let prop in props) { for (let prop in props) {
const [bindingIndex, nonMinifiedName] = props[prop]; const [bindingIndex, nonMinifiedName] = props[prop];
bindings[bindingIndex] = { bindings[bindingIndex] = {
type: BindingType.ProviderProperty, type: BindingType.DirectiveProperty,
name: prop, nonMinifiedName, name: prop, nonMinifiedName,
securityContext: undefined, securityContext: undefined,
suffix: undefined suffix: undefined
}; };
} }
} }
const outputDefs: ProviderOutputDef[] = []; const outputDefs: DirectiveOutputDef[] = [];
if (outputs) { if (outputs) {
for (let propName in outputs) { for (let propName in outputs) {
outputDefs.push({propName, eventName: outputs[propName]}); outputDefs.push({propName, eventName: outputs[propName]});
} }
} }
return _def(
NodeType.Directive, flags, matchedQueries, childCount, ProviderType.Class, ctor, ctor, deps,
bindings, outputDefs, component);
}
export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
return _def(NodeType.Pipe, flags, null, 0, ProviderType.Class, ctor, ctor, deps);
}
export function providerDef(
flags: NodeFlags, matchedQueries: [string, QueryValueType][], type: ProviderType, token: any,
value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
return _def(NodeType.Provider, flags, matchedQueries, 0, type, token, value, deps);
}
export function _def(
type: NodeType, flags: NodeFlags, matchedQueries: [string, QueryValueType][],
childCount: number, providerType: ProviderType, token: any, value: any,
deps: ([DepFlags, any] | any)[], bindings?: BindingDef[], outputs?: DirectiveOutputDef[],
component?: () => ViewDefinition): NodeDef {
const matchedQueryDefs: {[queryId: string]: QueryValueType} = {};
if (matchedQueries) {
matchedQueries.forEach(([queryId, valueType]) => { matchedQueryDefs[queryId] = valueType; });
}
if (!outputs) {
outputs = [];
}
if (!bindings) {
bindings = [];
}
const depDefs: DepDef[] = deps.map(value => { const depDefs: DepDef[] = deps.map(value => {
let token: any; let token: any;
let flags: DepFlags; let flags: DepFlags;
@ -86,7 +96,7 @@ export function _providerDef(
} }
return { return {
type: NodeType.Provider, type,
// will bet set by the view definition // will bet set by the view definition
index: undefined, index: undefined,
reverseChildIndex: undefined, reverseChildIndex: undefined,
@ -99,14 +109,13 @@ export function _providerDef(
flags, flags,
matchedQueries: matchedQueryDefs, matchedQueries: matchedQueryDefs,
ngContentIndex: undefined, childCount, bindings, ngContentIndex: undefined, childCount, bindings,
disposableCount: outputDefs.length, disposableCount: outputs.length,
element: undefined, element: undefined,
provider: { provider: {
type, type: providerType,
token, token,
tokenKey: tokenKey(token), value, tokenKey: tokenKey(token), value,
deps: depDefs, deps: depDefs, outputs, component
outputs: outputDefs, component
}, },
text: undefined, text: undefined,
pureExpression: undefined, pureExpression: undefined,
@ -116,97 +125,115 @@ export function _providerDef(
} }
export function createProviderInstance(view: ViewData, def: NodeDef): any { export function createProviderInstance(view: ViewData, def: NodeDef): any {
return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : _createProviderInstance(view, def);
}
export function createPipeInstance(view: ViewData, def: NodeDef): any {
// deps are looked up from component.
let compView = view;
while (compView.parent && !isComponentView(compView)) {
compView = compView.parent;
}
// pipes are always eager and classes!
return createClass(
compView.parent, compView.parentIndex, viewParentElIndex(compView), def.provider.value,
def.provider.deps);
}
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
const providerDef = def.provider; const providerDef = def.provider;
return def.flags & NodeFlags.LazyProvider ? NOT_CREATED : createInstance(view, def); // directives are always eager and classes!
const instance = createClass(view, def.index, def.parent, def.provider.value, def.provider.deps);
if (providerDef.outputs.length) {
for (let i = 0; i < providerDef.outputs.length; i++) {
const output = providerDef.outputs[i];
const subscription = instance[output.propName].subscribe(
eventHandlerClosure(view, def.parent, output.eventName));
view.disposables[def.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
}
}
return instance;
} }
function eventHandlerClosure(view: ViewData, index: number, eventName: string) { function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
return (event: any) => dispatchEvent(view, index, eventName, event); return (event: any) => dispatchEvent(view, index, eventName, event);
} }
export function checkAndUpdateProviderInline( export function checkAndUpdateDirectiveInline(
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
v7: any, v8: any, v9: any) { v7: any, v8: any, v9: any) {
const provider = asProviderData(view, def.index).instance; const providerData = asProviderData(view, def.index);
const directive = providerData.instance;
let changes: SimpleChanges; let changes: SimpleChanges;
// Note: fallthrough is intended! // Note: fallthrough is intended!
switch (def.bindings.length) { switch (def.bindings.length) {
case 10: case 10:
changes = checkAndUpdateProp(view, provider, def, 9, v9, changes); changes = checkAndUpdateProp(view, providerData, def, 9, v9, changes);
case 9: case 9:
changes = checkAndUpdateProp(view, provider, def, 8, v8, changes); changes = checkAndUpdateProp(view, providerData, def, 8, v8, changes);
case 8: case 8:
changes = checkAndUpdateProp(view, provider, def, 7, v7, changes); changes = checkAndUpdateProp(view, providerData, def, 7, v7, changes);
case 7: case 7:
changes = checkAndUpdateProp(view, provider, def, 6, v6, changes); changes = checkAndUpdateProp(view, providerData, def, 6, v6, changes);
case 6: case 6:
changes = checkAndUpdateProp(view, provider, def, 5, v5, changes); changes = checkAndUpdateProp(view, providerData, def, 5, v5, changes);
case 5: case 5:
changes = checkAndUpdateProp(view, provider, def, 4, v4, changes); changes = checkAndUpdateProp(view, providerData, def, 4, v4, changes);
case 4: case 4:
changes = checkAndUpdateProp(view, provider, def, 3, v3, changes); changes = checkAndUpdateProp(view, providerData, def, 3, v3, changes);
case 3: case 3:
changes = checkAndUpdateProp(view, provider, def, 2, v2, changes); changes = checkAndUpdateProp(view, providerData, def, 2, v2, changes);
case 2: case 2:
changes = checkAndUpdateProp(view, provider, def, 1, v1, changes); changes = checkAndUpdateProp(view, providerData, def, 1, v1, changes);
case 1: case 1:
changes = checkAndUpdateProp(view, provider, def, 0, v0, changes); changes = checkAndUpdateProp(view, providerData, def, 0, v0, changes);
} }
if (changes) { if (changes) {
provider.ngOnChanges(changes); directive.ngOnChanges(changes);
} }
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
provider.ngOnInit(); directive.ngOnInit();
} }
if (def.flags & NodeFlags.DoCheck) { if (def.flags & NodeFlags.DoCheck) {
provider.ngDoCheck(); directive.ngDoCheck();
} }
} }
export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, values: any[]) { export function checkAndUpdateDirectiveDynamic(view: ViewData, def: NodeDef, values: any[]) {
const provider = asProviderData(view, def.index).instance; const providerData = asProviderData(view, def.index);
const directive = providerData.instance;
let changes: SimpleChanges; let changes: SimpleChanges;
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
changes = checkAndUpdateProp(view, provider, def, i, values[i], changes); changes = checkAndUpdateProp(view, providerData, def, i, values[i], changes);
} }
if (changes) { if (changes) {
provider.ngOnChanges(changes); directive.ngOnChanges(changes);
} }
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
provider.ngOnInit(); directive.ngOnInit();
} }
if (def.flags & NodeFlags.DoCheck) { if (def.flags & NodeFlags.DoCheck) {
provider.ngDoCheck(); directive.ngDoCheck();
} }
} }
function createInstance(view: ViewData, nodeDef: NodeDef): any { function _createProviderInstance(view: ViewData, def: NodeDef): any {
const providerDef = nodeDef.provider; const providerDef = def.provider;
let injectable: any; let injectable: any;
switch (providerDef.type) { switch (providerDef.type) {
case ProviderType.Class: case ProviderType.Class:
injectable = injectable = createClass(view, def.index, def.parent, providerDef.value, providerDef.deps);
createClass(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps);
break; break;
case ProviderType.Factory: case ProviderType.Factory:
injectable = injectable = callFactory(view, def.index, def.parent, providerDef.value, providerDef.deps);
callFactory(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps);
break; break;
case ProviderType.UseExisting: case ProviderType.UseExisting:
injectable = resolveDep(view, nodeDef.index, nodeDef.parent, providerDef.deps[0]); injectable = resolveDep(view, def.index, def.parent, providerDef.deps[0]);
break; break;
case ProviderType.Value: case ProviderType.Value:
injectable = providerDef.value; injectable = providerDef.value;
break; break;
} }
if (providerDef.outputs.length) {
for (let i = 0; i < providerDef.outputs.length; i++) {
const output = providerDef.outputs[i];
const subscription = injectable[output.propName].subscribe(
eventHandlerClosure(view, nodeDef.parent, output.eventName));
view.disposables[nodeDef.disposableIndex + i] = subscription.unsubscribe.bind(subscription);
}
}
return injectable; return injectable;
} }
@ -291,7 +318,7 @@ export function resolveDep(
requestNodeIndex = null; requestNodeIndex = null;
elIndex = view.def.nodes[elIndex].parent; elIndex = view.def.nodes[elIndex].parent;
while (elIndex == null && view) { while (elIndex == null && view) {
elIndex = viewParentDiIndex(view); elIndex = viewParentElIndex(view);
view = view.parent; view = view.parent;
} }
} }
@ -334,20 +361,20 @@ export function resolveDep(
if (providerIndex != null) { if (providerIndex != null) {
const providerData = asProviderData(view, providerIndex); const providerData = asProviderData(view, providerIndex);
if (providerData.instance === NOT_CREATED) { if (providerData.instance === NOT_CREATED) {
providerData.instance = createInstance(view, view.def.nodes[providerIndex]); providerData.instance = _createProviderInstance(view, view.def.nodes[providerIndex]);
} }
return providerData.instance; return providerData.instance;
} }
} }
requestNodeIndex = null; requestNodeIndex = null;
elIndex = viewParentDiIndex(view); elIndex = viewParentElIndex(view);
view = view.parent; view = view.parent;
} }
return startView.root.injector.get(depDef.token, notFoundValue); return startView.root.injector.get(depDef.token, notFoundValue);
} }
function checkAndUpdateProp( function checkAndUpdateProp(
view: ViewData, provider: any, def: NodeDef, bindingIdx: number, value: any, view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any,
changes: SimpleChanges): SimpleChanges { changes: SimpleChanges): SimpleChanges {
let change: SimpleChange; let change: SimpleChange;
let changed: boolean; let changed: boolean;
@ -361,13 +388,18 @@ function checkAndUpdateProp(
changed = checkAndUpdateBinding(view, def, bindingIdx, value); changed = checkAndUpdateBinding(view, def, bindingIdx, value);
} }
if (changed) { if (changed) {
value = unwrapValue(value); if (def.flags & NodeFlags.HasComponent) {
const compView = providerData.componentView;
if (compView.def.flags & ViewFlags.OnPush) {
compView.state |= ViewState.ChecksEnabled;
}
}
const binding = def.bindings[bindingIdx]; const binding = def.bindings[bindingIdx];
const propName = binding.name; const propName = binding.name;
// Note: This is still safe with Closure Compiler as // Note: This is still safe with Closure Compiler as
// the user passed in the property name as an object has to `providerDef`, // the user passed in the property name as an object has to `providerDef`,
// so Closure Compiler will have renamed the property correctly already. // so Closure Compiler will have renamed the property correctly already.
provider[propName] = value; providerData.instance[propName] = value;
if (change) { if (change) {
changes = changes || {}; changes = changes || {};
changes[binding.nonMinifiedName] = change; changes[binding.nonMinifiedName] = change;
@ -382,7 +414,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node
} }
const len = view.def.nodes.length; const len = view.def.nodes.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
// We use the provider post order to call providers of children first. // We use the reverse child oreder to call providers of children first.
const nodeDef = view.def.reverseChildNodes[i]; const nodeDef = view.def.reverseChildNodes[i];
const nodeIndex = nodeDef.index; const nodeIndex = nodeDef.index;
if (nodeDef.flags & lifecycles) { if (nodeDef.flags & lifecycles) {

View File

@ -7,24 +7,22 @@
*/ */
import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, Services, ViewData, asPureExpressionData} from './types'; import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, ProviderData, PureExpressionData, PureExpressionType, Services, ViewData, asPureExpressionData} from './types';
import {checkAndUpdateBinding, tokenKey, unwrapValue} from './util'; import {checkAndUpdateBinding, tokenKey} from './util';
export function purePipeDef(pipeToken: any, argCount: number): NodeDef { export function purePipeDef(argCount: number): NodeDef {
return _pureExpressionDef( // argCount + 1 to include the pipe as first arg
PureExpressionType.Pipe, new Array(argCount), return _pureExpressionDef(PureExpressionType.Pipe, new Array(argCount + 1));
{token: pipeToken, tokenKey: tokenKey(pipeToken), flags: DepFlags.None});
} }
export function pureArrayDef(argCount: number): NodeDef { export function pureArrayDef(argCount: number): NodeDef {
return _pureExpressionDef(PureExpressionType.Array, new Array(argCount), undefined); return _pureExpressionDef(PureExpressionType.Array, new Array(argCount));
} }
export function pureObjectDef(propertyNames: string[]): NodeDef { export function pureObjectDef(propertyNames: string[]): NodeDef {
return _pureExpressionDef(PureExpressionType.Object, propertyNames, undefined); return _pureExpressionDef(PureExpressionType.Object, propertyNames);
} }
function _pureExpressionDef( function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): NodeDef {
type: PureExpressionType, propertyNames: string[], pipeDep: DepDef): NodeDef {
const bindings: BindingDef[] = new Array(propertyNames.length); const bindings: BindingDef[] = new Array(propertyNames.length);
for (let i = 0; i < propertyNames.length; i++) { for (let i = 0; i < propertyNames.length; i++) {
const prop = propertyNames[i]; const prop = propertyNames[i];
@ -55,17 +53,14 @@ function _pureExpressionDef(
element: undefined, element: undefined,
provider: undefined, provider: undefined,
text: undefined, text: undefined,
pureExpression: {type, pipeDep}, pureExpression: {type},
query: undefined, query: undefined,
ngContent: undefined ngContent: undefined
}; };
} }
export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData { export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData {
const pipe = def.pureExpression.pipeDep ? return {value: undefined};
Services.resolveDep(view, def.index, def.parent, def.pureExpression.pipeDep) :
undefined;
return {value: undefined, pipe};
} }
export function checkAndUpdatePureExpressionInline( export function checkAndUpdatePureExpressionInline(
@ -99,17 +94,6 @@ export function checkAndUpdatePureExpressionInline(
const data = asPureExpressionData(view, def.index); const data = asPureExpressionData(view, def.index);
if (changed) { if (changed) {
v0 = unwrapValue(v0);
v1 = unwrapValue(v1);
v2 = unwrapValue(v2);
v3 = unwrapValue(v3);
v4 = unwrapValue(v4);
v5 = unwrapValue(v5);
v6 = unwrapValue(v6);
v7 = unwrapValue(v7);
v8 = unwrapValue(v8);
v9 = unwrapValue(v9);
let value: any; let value: any;
switch (def.pureExpression.type) { switch (def.pureExpression.type) {
case PureExpressionType.Array: case PureExpressionType.Array:
@ -165,36 +149,37 @@ export function checkAndUpdatePureExpressionInline(
} }
break; break;
case PureExpressionType.Pipe: case PureExpressionType.Pipe:
const pipe = v0;
switch (bindings.length) { switch (bindings.length) {
case 10: case 10:
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8, v9);
break; break;
case 9: case 9:
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8); value = pipe.transform(v1, v2, v3, v4, v5, v6, v7, v8);
break; break;
case 8: case 8:
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7); value = pipe.transform(v1, v2, v3, v4, v5, v6, v7);
break; break;
case 7: case 7:
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6); value = pipe.transform(v1, v2, v3, v4, v5, v6);
break; break;
case 6: case 6:
value = data.pipe.transform(v0, v1, v2, v3, v4, v5); value = pipe.transform(v1, v2, v3, v4, v5);
break; break;
case 5: case 5:
value = data.pipe.transform(v0, v1, v2, v3, v4); value = pipe.transform(v1, v2, v3, v4);
break; break;
case 4: case 4:
value = data.pipe.transform(v0, v1, v2, v3); value = pipe.transform(v1, v2, v3);
break; break;
case 3: case 3:
value = data.pipe.transform(v0, v1, v2); value = pipe.transform(v1, v2);
break; break;
case 2: case 2:
value = data.pipe.transform(v0, v1); value = pipe.transform(v1);
break; break;
case 1: case 1:
value = data.pipe.transform(v0); value = pipe.transform(v0);
break; break;
} }
break; break;
@ -219,23 +204,18 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef
let value: any; let value: any;
switch (def.pureExpression.type) { switch (def.pureExpression.type) {
case PureExpressionType.Array: case PureExpressionType.Array:
value = new Array(values.length); value = values;
for (let i = 0; i < values.length; i++) {
value[i] = unwrapValue(values[i]);
}
break; break;
case PureExpressionType.Object: case PureExpressionType.Object:
value = {}; value = {};
for (let i = 0; i < values.length; i++) { for (let i = 0; i < values.length; i++) {
value[bindings[i].name] = unwrapValue(values[i]); value[bindings[i].name] = values[i];
} }
break; break;
case PureExpressionType.Pipe: case PureExpressionType.Pipe:
const params = new Array(values.length); const pipe = values[0];
for (let i = 0; i < values.length; i++) { const params = values.slice(1);
params[i] = unwrapValue(values[i]); value = (<any>pipe.transform)(...params);
}
value = (<any>data.pipe.transform)(...params);
break; break;
} }
data.value = value; data.value = value;

View File

@ -13,7 +13,7 @@ import {ViewContainerRef} from '../linker/view_container_ref';
import {createTemplateRef, createViewContainerRef} from './refs'; import {createTemplateRef, createViewContainerRef} from './refs';
import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types'; import {NodeDef, NodeFlags, NodeType, QueryBindingDef, QueryBindingType, QueryDef, QueryValueType, Services, ViewData, asElementData, asProviderData, asQueryList} from './types';
import {declaredViewContainer} from './util'; import {declaredViewContainer, viewParentElIndex} from './util';
export function queryDef( export function queryDef(
flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef { flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef {
@ -54,16 +54,18 @@ export function createQuery(): QueryList<any> {
} }
export function dirtyParentQuery(queryId: string, view: ViewData) { export function dirtyParentQuery(queryId: string, view: ViewData) {
let nodeIndex = view.parentIndex; let elIndex = viewParentElIndex(view);
view = view.parent; view = view.parent;
let queryIdx: number; let queryIdx: number;
while (view) { while (view) {
const elementDef = view.def.nodes[nodeIndex]; if (elIndex != null) {
const elementDef = view.def.nodes[elIndex];
queryIdx = elementDef.element.providerIndices[queryId]; queryIdx = elementDef.element.providerIndices[queryId];
if (queryIdx != null) { if (queryIdx != null) {
break; break;
} }
nodeIndex = view.parentIndex; }
elIndex = viewParentElIndex(view);
view = view.parent; view = view.parent;
} }
if (!view) { if (!view) {

View File

@ -19,8 +19,8 @@ import {Sanitizer, SecurityContext} from '../security';
import {Type} from '../type'; import {Type} from '../type';
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types';
import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentDiIndex} from './util'; import {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentElIndex} from './util';
const EMPTY_CONTEXT = new Object(); const EMPTY_CONTEXT = new Object();
@ -53,7 +53,7 @@ class ComponentFactory_ implements ComponentFactory<any> {
const len = viewDef.nodes.length; const len = viewDef.nodes.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const nodeDef = viewDef.nodes[i]; const nodeDef = viewDef.nodes[i];
if (nodeDef.provider && nodeDef.provider.component) { if (nodeDef.flags & NodeFlags.HasComponent) {
componentNodeIndex = i; componentNodeIndex = i;
break; break;
} }
@ -99,7 +99,7 @@ class ViewContainerRef_ implements ViewContainerRef {
let view = this._view; let view = this._view;
let elIndex = view.def.nodes[this._elIndex].parent; let elIndex = view.def.nodes[this._elIndex].parent;
while (elIndex == null && view) { while (elIndex == null && view) {
elIndex = viewParentDiIndex(view); elIndex = viewParentElIndex(view);
view = view.parent; view = view.parent;
} }
return view ? new Injector_(view, elIndex) : this._view.root.injector; return view ? new Injector_(view, elIndex) : this._view.root.injector;

View File

@ -19,8 +19,8 @@ import {resolveDep} from './provider';
import {getQueryValue} from './query'; import {getQueryValue} from './query';
import {createInjector} from './refs'; import {createInjector} from './refs';
import {DirectDomRenderer, LegacyRendererAdapter} from './renderer'; import {DirectDomRenderer, LegacyRendererAdapter} from './renderer';
import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, asElementData, asProviderData} from './types'; import {ArgumentType, BindingType, DebugContext, DepFlags, ElementData, NodeCheckFn, NodeData, NodeDef, NodeFlags, NodeType, RendererV2, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewState, ViewUpdateFn, asElementData, asProviderData} from './types';
import {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentDiIndex} from './util'; import {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentElIndex} from './util';
import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view';
import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach';
@ -44,7 +44,8 @@ export function initServicesIfNeeded() {
Services.resolveDep = services.resolveDep; Services.resolveDep = services.resolveDep;
Services.createDebugContext = services.createDebugContext; Services.createDebugContext = services.createDebugContext;
Services.handleEvent = services.handleEvent; Services.handleEvent = services.handleEvent;
Services.updateView = services.updateView; Services.updateDirectives = services.updateDirectives;
Services.updateRenderer = services.updateRenderer;
} }
function createProdServices() { function createProdServices() {
@ -62,7 +63,9 @@ function createProdServices() {
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) => handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) =>
view.def.handleEvent(view, nodeIndex, eventName, event), view.def.handleEvent(view, nodeIndex, eventName, event),
updateView: (check: NodeCheckFn, view: ViewData) => view.def.update(check, view) updateDirectives: (check: NodeCheckFn, view: ViewData) =>
view.def.updateDirectives(check, view),
updateRenderer: (check: NodeCheckFn, view: ViewData) => view.def.updateRenderer(check, view),
}; };
} }
@ -80,7 +83,8 @@ function createDebugServices() {
resolveDep: resolveDep, resolveDep: resolveDep,
createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex),
handleEvent: debugHandleEvent, handleEvent: debugHandleEvent,
updateView: debugUpdateView updateDirectives: debugUpdateDirectives,
updateRenderer: debugUpdateRenderer
}; };
} }
@ -152,36 +156,55 @@ function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string,
'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]); 'handleEvent', view.def.handleEvent, null, [view, nodeIndex, eventName, event]);
} }
function debugUpdateView(check: NodeCheckFn, view: ViewData) { function debugUpdateDirectives(check: NodeCheckFn, view: ViewData) {
if (view.state & ViewState.Destroyed) { if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(_currentAction); throw viewDestroyedError(_currentAction);
} }
debugSetCurrentNode(view, nextNodeIndexWithBinding(view, 0)); debugSetCurrentNode(view, nextDirectiveWithBinding(view, 0));
return view.def.update(debugCheckFn, view); return view.def.updateDirectives(debugCheckDirectivesFn, view);
function debugCheckDirectivesFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
debugSetCurrentNode(view, nextDirectiveWithBinding(view, nodeIndex));
return result;
};
}
function debugUpdateRenderer(check: NodeCheckFn, view: ViewData) {
if (view.state & ViewState.Destroyed) {
throw viewDestroyedError(_currentAction);
}
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, 0));
return view.def.updateRenderer(debugCheckRenderNodeFn, view);
function debugCheckRenderNodeFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, ...values: any[]) {
const result = debugCheckFn(check, view, nodeIndex, argStyle, values);
debugSetCurrentNode(view, nextRenderNodeWithBinding(view, nodeIndex));
return result;
};
}
function debugCheckFn( function debugCheckFn(
view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, delegate: NodeCheckFn, view: ViewData, nodeIndex: number, argStyle: ArgumentType,
v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any) { givenValues: any[]) {
const values = argStyle === ArgumentType.Dynamic ? v0 : [].slice.call(arguments, 3); const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues;
const nodeDef = view.def.nodes[nodeIndex]; const nodeDef = view.def.nodes[nodeIndex];
for (let i = 0; i < nodeDef.bindings.length; i++) { for (let i = 0; i < nodeDef.bindings.length; i++) {
const binding = nodeDef.bindings[i]; const binding = nodeDef.bindings[i];
const value = values[i]; const value = values[i];
if ((binding.type === BindingType.ElementProperty || if ((binding.type === BindingType.ElementProperty ||
binding.type === BindingType.ProviderProperty) && binding.type === BindingType.DirectiveProperty) &&
checkBinding(view, nodeDef, i, value)) { checkBinding(view, nodeDef, i, value)) {
const elIndex = nodeDef.type === NodeType.Provider ? nodeDef.parent : nodeDef.index; const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index;
setBindingDebugInfo( setBindingDebugInfo(
view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName,
value); value);
} }
} }
const result = check(view, nodeIndex, <any>argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); return (<any>delegate)(view, nodeIndex, argStyle, ...givenValues);
debugSetCurrentNode(view, nextNodeIndexWithBinding(view, nodeIndex));
return result;
}; };
}
function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) { function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) {
const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`; const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`;
@ -203,16 +226,26 @@ function camelCaseToDashCase(input: string): string {
return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase()); return input.replace(CAMEL_CASE_REGEXP, (...m: any[]) => '-' + m[1].toLowerCase());
} }
function nextNodeIndexWithBinding(view: ViewData, nodeIndex: number): number { function nextDirectiveWithBinding(view: ViewData, nodeIndex: number): number {
for (let i = nodeIndex; i < view.def.nodes.length; i++) { for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i]; const nodeDef = view.def.nodes[i];
if (nodeDef.bindings && nodeDef.bindings.length) { if (nodeDef.type === NodeType.Directive && nodeDef.bindings && nodeDef.bindings.length) {
return i; return i;
} }
} }
return undefined; return undefined;
} }
function nextRenderNodeWithBinding(view: ViewData, nodeIndex: number): number {
for (let i = nodeIndex; i < view.def.nodes.length; i++) {
const nodeDef = view.def.nodes[i];
if ((nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text) && nodeDef.bindings &&
nodeDef.bindings.length) {
return i;
}
}
return undefined;
}
class DebugRenderer implements RendererV2 { class DebugRenderer implements RendererV2 {
constructor(private _delegate: RendererV2) {} constructor(private _delegate: RendererV2) {}
@ -282,7 +315,7 @@ class DebugContext_ implements DebugContext {
} }
if (elIndex == null) { if (elIndex == null) {
while (elIndex == null && elView) { while (elIndex == null && elView) {
elIndex = viewParentDiIndex(elView); elIndex = viewParentElIndex(elView);
elView = elView.parent; elView = elView.parent;
} }
} }
@ -320,7 +353,7 @@ class DebugContext_ implements DebugContext {
if (this.elDef) { if (this.elDef) {
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
const childDef = this.elView.def.nodes[i]; const childDef = this.elView.def.nodes[i];
if (childDef.type === NodeType.Provider) { if (childDef.type === NodeType.Provider || childDef.type === NodeType.Directive) {
tokens.push(childDef.provider.token); tokens.push(childDef.provider.token);
} }
i += childDef.childCount; i += childDef.childCount;
@ -335,7 +368,7 @@ class DebugContext_ implements DebugContext {
for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) { for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; i++) {
const childDef = this.elView.def.nodes[i]; const childDef = this.elView.def.nodes[i];
if (childDef.type === NodeType.Provider) { if (childDef.type === NodeType.Provider || childDef.type === NodeType.Directive) {
collectReferences(this.elView, childDef, references); collectReferences(this.elView, childDef, references);
} }
i += childDef.childCount; i += childDef.childCount;
@ -368,7 +401,7 @@ function findHostElement(view: ViewData): ElementData {
view = view.parent; view = view.parent;
} }
if (view.parent) { if (view.parent) {
const hostData = asElementData(view.parent, view.parentIndex); const hostData = asElementData(view.parent, viewParentElIndex(view));
return hostData; return hostData;
} }
return undefined; return undefined;
@ -397,6 +430,7 @@ function callWithDebugContext(action: string, fn: any, self: any, args: any[]) {
if (isViewDebugError(e) || !_currentView) { if (isViewDebugError(e) || !_currentView) {
throw e; throw e;
} }
_currentView.state |= ViewState.Errored;
throw viewWrappedDebugError(e, getCurrentDebugContext()); throw viewWrappedDebugError(e, getCurrentDebugContext());
} }
} }

View File

@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref';
import {looseIdentical} from '../facade/lang'; import {looseIdentical} from '../facade/lang';
import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types'; import {BindingDef, BindingType, DebugContext, NodeData, NodeDef, NodeFlags, NodeType, RootData, Services, TextData, ViewData, ViewFlags, asElementData, asTextData} from './types';
import {checkAndUpdateBinding, sliceErrorStack, unwrapValue} from './util'; import {checkAndUpdateBinding, sliceErrorStack} from './util';
export function textDef(ngContentIndex: number, constants: string[]): NodeDef { export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
// skip the call to sliceErrorStack itself + the call to this function. // skip the call to sliceErrorStack itself + the call to this function.
@ -18,7 +18,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef {
const bindings: BindingDef[] = new Array(constants.length - 1); const bindings: BindingDef[] = new Array(constants.length - 1);
for (let i = 1; i < constants.length; i++) { for (let i = 1; i < constants.length; i++) {
bindings[i - 1] = { bindings[i - 1] = {
type: BindingType.Interpolation, type: BindingType.TextInterpolation,
name: undefined, name: undefined,
nonMinifiedName: undefined, nonMinifiedName: undefined,
securityContext: undefined, securityContext: undefined,
@ -143,7 +143,6 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values:
} }
function _addInterpolationPart(value: any, binding: BindingDef): string { function _addInterpolationPart(value: any, binding: BindingDef): string {
value = unwrapValue(value);
const valueStr = value != null ? value.toString() : ''; const valueStr = value != null ? value.toString() : '';
return valueStr + binding.suffix; return valueStr + binding.suffix;
} }

View File

@ -23,7 +23,8 @@ import {Sanitizer, SecurityContext} from '../security';
export interface ViewDefinition { export interface ViewDefinition {
flags: ViewFlags; flags: ViewFlags;
component: ComponentDefinition; component: ComponentDefinition;
update: ViewUpdateFn; updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
handleEvent: ViewHandleEventFn; handleEvent: ViewHandleEventFn;
/** /**
* Order: Depth first. * Order: Depth first.
@ -124,7 +125,9 @@ export interface NodeDef {
export enum NodeType { export enum NodeType {
Element, Element,
Text, Text,
Directive,
Provider, Provider,
Pipe,
PureExpression, PureExpression,
Query, Query,
NgContent NgContent
@ -163,8 +166,8 @@ export enum BindingType {
ElementClass, ElementClass,
ElementStyle, ElementStyle,
ElementProperty, ElementProperty,
ProviderProperty, DirectiveProperty,
Interpolation, TextInterpolation,
PureExpressionProperty PureExpressionProperty
} }
@ -200,7 +203,7 @@ export interface ProviderDef {
tokenKey: string; tokenKey: string;
value: any; value: any;
deps: DepDef[]; deps: DepDef[];
outputs: ProviderOutputDef[]; outputs: DirectiveOutputDef[];
// closure to allow recursive components // closure to allow recursive components
component: ViewDefinitionFactory; component: ViewDefinitionFactory;
} }
@ -228,7 +231,7 @@ export enum DepFlags {
Value = 2 << 2 Value = 2 << 2
} }
export interface ProviderOutputDef { export interface DirectiveOutputDef {
propName: string; propName: string;
eventName: string; eventName: string;
} }
@ -238,10 +241,7 @@ export interface TextDef {
source: string; source: string;
} }
export interface PureExpressionDef { export interface PureExpressionDef { type: PureExpressionType; }
type: PureExpressionType;
pipeDep: DepDef;
}
export enum PureExpressionType { export enum PureExpressionType {
Array, Array,
@ -285,8 +285,7 @@ export interface NgContentDef {
export interface ViewData { export interface ViewData {
def: ViewDefinition; def: ViewDefinition;
root: RootData; root: RootData;
// index of parent element / anchor. Not the index // index of component provider / anchor.
// of the provider with the component view.
parentIndex: number; parentIndex: number;
parent: ViewData; parent: ViewData;
component: any; component: any;
@ -385,10 +384,7 @@ export function asProviderData(view: ViewData, index: number): ProviderData {
* *
* Attention: Adding fields to this is performance sensitive! * Attention: Adding fields to this is performance sensitive!
*/ */
export interface PureExpressionData { export interface PureExpressionData { value: any; }
value: any;
pipe: PipeTransform;
}
/** /**
* Accessor for view.nodes, enforcing that every usage site stays monomorphic. * Accessor for view.nodes, enforcing that every usage site stays monomorphic.
@ -493,7 +489,8 @@ export interface Services {
notFoundValue?: any): any; notFoundValue?: any): any;
createDebugContext(view: ViewData, nodeIndex: number): DebugContext; createDebugContext(view: ViewData, nodeIndex: number): DebugContext;
handleEvent: ViewHandleEventFn; handleEvent: ViewHandleEventFn;
updateView: ViewUpdateFn; updateDirectives: ViewUpdateFn;
updateRenderer: ViewUpdateFn;
} }
/** /**
@ -513,5 +510,6 @@ export const Services: Services = {
resolveDep: undefined, resolveDep: undefined,
createDebugContext: undefined, createDebugContext: undefined,
handleEvent: undefined, handleEvent: undefined,
updateView: undefined, updateDirectives: undefined,
updateRenderer: undefined,
}; };

View File

@ -30,16 +30,28 @@ export function tokenKey(token: any): string {
return key; return key;
} }
let unwrapCounter = 0;
export function unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
value = value.wrapped;
unwrapCounter++;
}
return value;
}
export function checkBinding( export function checkBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
return !!(view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value); return unwrapCounter > 0 || !!(view.state & ViewState.FirstCheck) ||
!devModeEqual(oldValue, value);
} }
export function checkBindingNoChanges( export function checkBindingNoChanges(
view: ViewData, def: NodeDef, bindingIdx: number, value: any) { view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
const oldValue = view.oldValues[def.bindingIndex + bindingIdx]; const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) { if (unwrapCounter || (view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
unwrapCounter = 0;
throw expressionChangedAfterItHasBeenCheckedError( throw expressionChangedAfterItHasBeenCheckedError(
Services.createDebugContext(view, def.index), oldValue, value, Services.createDebugContext(view, def.index), oldValue, value,
(view.state & ViewState.FirstCheck) !== 0); (view.state & ViewState.FirstCheck) !== 0);
@ -49,15 +61,10 @@ export function checkBindingNoChanges(
export function checkAndUpdateBinding( export function checkAndUpdateBinding(
view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean {
const oldValues = view.oldValues; const oldValues = view.oldValues;
if ((view.state & ViewState.FirstCheck) || if (unwrapCounter || (view.state & ViewState.FirstCheck) ||
!looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) { !looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) {
unwrapCounter = 0;
oldValues[def.bindingIndex + bindingIdx] = value; oldValues[def.bindingIndex + bindingIdx] = value;
if (def.flags & NodeFlags.HasComponent) {
const compView = asProviderData(view, def.index).componentView;
if (compView.def.flags & ViewFlags.OnPush) {
compView.state |= ViewState.ChecksEnabled;
}
}
return true; return true;
} }
return false; return false;
@ -75,13 +82,6 @@ export function dispatchEvent(
return Services.handleEvent(view, nodeIndex, eventName, event); return Services.handleEvent(view, nodeIndex, eventName, event);
} }
export function unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
value = value.wrapped;
}
return value;
}
export function declaredViewContainer(view: ViewData): ElementData { export function declaredViewContainer(view: ViewData): ElementData {
if (view.parent) { if (view.parent) {
const parentView = view.parent; const parentView = view.parent;
@ -91,16 +91,17 @@ export function declaredViewContainer(view: ViewData): ElementData {
} }
/** /**
* for component views, this is the same as parentIndex. * for component views, this is the host element.
* for embedded views, this is the index of the parent node * for embedded views, this is the index of the parent node
* that contains the view container. * that contains the view container.
*/ */
export function viewParentDiIndex(view: ViewData): number { export function viewParentElIndex(view: ViewData): number {
if (view.parent && view.context !== view.component) { const parentView = view.parent;
const parentNodeDef = view.parent.def.nodes[view.parentIndex]; if (parentView) {
return parentNodeDef.parent; return parentView.def.nodes[view.parentIndex].parent;
} else {
return null;
} }
return view.parentIndex;
} }
export function renderNode(view: ViewData, def: NodeDef): any { export function renderNode(view: ViewData, def: NodeDef): any {
@ -119,6 +120,8 @@ export function nodeValue(view: ViewData, index: number): any {
return asElementData(view, def.index).renderElement; return asElementData(view, def.index).renderElement;
case NodeType.Text: case NodeType.Text:
return asTextData(view, def.index).renderText; return asTextData(view, def.index).renderText;
case NodeType.Directive:
case NodeType.Pipe:
case NodeType.Provider: case NodeType.Provider:
return asProviderData(view, def.index).instance; return asProviderData(view, def.index).instance;
} }
@ -183,7 +186,10 @@ export function visitRootRenderNodes(
const len = view.def.nodes.length; const len = view.def.nodes.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const nodeDef = view.def.nodes[i]; const nodeDef = view.def.nodes[i];
if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text ||
nodeDef.type === NodeType.NgContent) {
visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target); visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target);
}
// jump to next sibling // jump to next sibling
i += nodeDef.childCount; i += nodeDef.childCount;
} }
@ -197,7 +203,7 @@ export function visitProjectedRenderNodes(
compView = compView.parent; compView = compView.parent;
} }
const hostView = compView.parent; const hostView = compView.parent;
const hostElDef = hostView.def.nodes[compView.parentIndex]; const hostElDef = hostView.def.nodes[viewParentElIndex(compView)];
const startIndex = hostElDef.index + 1; const startIndex = hostElDef.index + 1;
const endIndex = hostElDef.index + hostElDef.childCount; const endIndex = hostElDef.index + hostElDef.childCount;
for (let i = startIndex; i <= endIndex; i++) { for (let i = startIndex; i <= endIndex; i++) {

View File

@ -11,19 +11,19 @@ import {ViewEncapsulation} from '../metadata/view';
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {appendNgContent} from './ng_content'; import {appendNgContent} from './ng_content';
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProviderInstance} from './provider'; import {callLifecycleHooksChildrenFirst, checkAndUpdateDirectiveDynamic, checkAndUpdateDirectiveInline, createDirectiveInstance, createPipeInstance, createProviderInstance} from './provider';
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression'; import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
import {checkAndUpdateQuery, createQuery, queryDef} from './query'; import {checkAndUpdateQuery, createQuery, queryDef} from './query';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text'; import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types'; import {ArgumentType, ComponentDefinition, ElementDef, NodeData, NodeDef, NodeFlags, NodeType, ProviderData, ProviderDef, RootData, Services, TextDef, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asProviderData, asPureExpressionData, asQueryList} from './types';
import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition} from './util'; import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition, viewParentElIndex} from './util';
const NOOP = (): any => undefined; const NOOP = (): any => undefined;
export function viewDef( export function viewDef(
flags: ViewFlags, nodesWithoutIndices: NodeDef[], update?: ViewUpdateFn, flags: ViewFlags, nodesWithoutIndices: NodeDef[], updateDirectives?: ViewUpdateFn,
handleEvent?: ViewHandleEventFn, compId?: string, encapsulation?: ViewEncapsulation, updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string,
styles?: string[]): ViewDefinition { encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition {
// clone nodes and set auto calculated values // clone nodes and set auto calculated values
if (nodesWithoutIndices.length === 0) { if (nodesWithoutIndices.length === 0) {
throw new Error(`Illegal State: Views without nodes are not allowed!`); throw new Error(`Illegal State: Views without nodes are not allowed!`);
@ -83,7 +83,7 @@ export function viewDef(
if (!currentParent) { if (!currentParent) {
lastRootNode = node; lastRootNode = node;
} }
if (node.provider) { if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
currentParent.element.providerIndices[node.provider.tokenKey] = i; currentParent.element.providerIndices[node.provider.tokenKey] = i;
} }
if (node.query) { if (node.query) {
@ -108,7 +108,8 @@ export function viewDef(
nodeFlags: viewNodeFlags, nodeFlags: viewNodeFlags,
nodeMatchedQueries: viewMatchedQueries, flags, nodeMatchedQueries: viewMatchedQueries, flags,
nodes: nodes, reverseChildNodes, nodes: nodes, reverseChildNodes,
update: update || NOOP, updateDirectives: updateDirectives || NOOP,
updateRenderer: updateRenderer || NOOP,
handleEvent: handleEvent || NOOP, handleEvent: handleEvent || NOOP,
component: componentDef, component: componentDef,
bindingCount: viewBindingCount, bindingCount: viewBindingCount,
@ -172,18 +173,18 @@ function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) {
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`); `Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
} }
} }
if (node.provider) { if (node.type === NodeType.Provider || node.type === NodeType.Directive) {
const parentType = parent ? parent.type : null; const parentType = parent ? parent.type : null;
if (parentType !== NodeType.Element) { if (parentType !== NodeType.Element) {
throw new Error( throw new Error(
`Illegal State: Provider nodes need to be children of elements or anchors, at index ${node.index}!`); `Illegal State: Provider/Directive nodes need to be children of elements or anchors, at index ${node.index}!`);
} }
} }
if (node.query) { if (node.query) {
const parentType = parent ? parent.type : null; const parentType = parent ? parent.type : null;
if (parentType !== NodeType.Provider) { if (parentType !== NodeType.Directive) {
throw new Error( throw new Error(
`Illegal State: Query nodes need to be children of providers, at index ${node.index}!`); `Illegal State: Query nodes need to be children of directives, at index ${node.index}!`);
} }
} }
if (node.childCount) { if (node.childCount) {
@ -282,7 +283,7 @@ function initView(view: ViewData, component: any, context: any) {
function createViewNodes(view: ViewData) { function createViewNodes(view: ViewData) {
let renderHost: any; let renderHost: any;
if (isComponentView(view)) { if (isComponentView(view)) {
renderHost = asElementData(view.parent, view.parentIndex).renderElement; renderHost = asElementData(view.parent, viewParentElIndex(view)).renderElement;
} }
const def = view.def; const def = view.def;
@ -297,23 +298,36 @@ function createViewNodes(view: ViewData) {
case NodeType.Text: case NodeType.Text:
nodes[i] = createText(view, renderHost, nodeDef) as any; nodes[i] = createText(view, renderHost, nodeDef) as any;
break; break;
case NodeType.Provider: case NodeType.Provider: {
if (nodeDef.provider.component) { const instance = createProviderInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance};
nodes[i] = providerData as any;
break;
}
case NodeType.Pipe: {
const instance = createPipeInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance};
nodes[i] = providerData as any;
break;
}
case NodeType.Directive: {
if (nodeDef.flags & NodeFlags.HasComponent) {
// Components can inject a ChangeDetectorRef that needs a references to // Components can inject a ChangeDetectorRef that needs a references to
// the component view. Therefore, we create the component view first // the component view. Therefore, we create the component view first
// and set the ProviderData in ViewData, and then instantiate the provider. // and set the ProviderData in ViewData, and then instantiate the provider.
const componentView = createView( const componentView = createView(
view.root, view, nodeDef.parent, resolveViewDefinition(nodeDef.provider.component)); view.root, view, nodeDef.index, resolveViewDefinition(nodeDef.provider.component));
const providerData = <ProviderData>{componentView, instance: undefined}; const providerData = <ProviderData>{componentView, instance: undefined};
nodes[i] = providerData as any; nodes[i] = providerData as any;
const instance = providerData.instance = createProviderInstance(view, nodeDef); const instance = providerData.instance = createDirectiveInstance(view, nodeDef);
initView(componentView, instance, instance); initView(componentView, instance, instance);
} else { } else {
const instance = createProviderInstance(view, nodeDef); const instance = createDirectiveInstance(view, nodeDef);
const providerData = <ProviderData>{componentView: undefined, instance}; const providerData = <ProviderData>{componentView: undefined, instance};
nodes[i] = providerData as any; nodes[i] = providerData as any;
} }
break; break;
}
case NodeType.PureExpression: case NodeType.PureExpression:
nodes[i] = createPureExpression(view, nodeDef) as any; nodes[i] = createPureExpression(view, nodeDef) as any;
break; break;
@ -333,21 +347,25 @@ function createViewNodes(view: ViewData) {
} }
export function checkNoChangesView(view: ViewData) { export function checkNoChangesView(view: ViewData) {
Services.updateView(checkNoChangesNode, view); Services.updateDirectives(checkNoChangesNode, view);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges);
Services.updateRenderer(checkNoChangesNode, view);
execComponentViewsAction(view, ViewAction.CheckNoChanges); execComponentViewsAction(view, ViewAction.CheckNoChanges);
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges);
} }
export function checkAndUpdateView(view: ViewData) { export function checkAndUpdateView(view: ViewData) {
Services.updateView(checkAndUpdateNode, view); Services.updateDirectives(checkAndUpdateNode, view);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate);
callLifecycleHooksChildrenFirst( callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterContentChecked | view, NodeFlags.AfterContentChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0)); (view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0));
Services.updateRenderer(checkAndUpdateNode, view);
execComponentViewsAction(view, ViewAction.CheckAndUpdate); execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate); execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckAndUpdate);
@ -380,8 +398,8 @@ function checkAndUpdateNodeInline(
return checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); return checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
case NodeType.Text: case NodeType.Text:
return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
case NodeType.Provider: case NodeType.Directive:
return checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); return checkAndUpdateDirectiveInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
case NodeType.PureExpression: case NodeType.PureExpression:
return checkAndUpdatePureExpressionInline( return checkAndUpdatePureExpressionInline(
view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
@ -395,8 +413,8 @@ function checkAndUpdateNodeDynamic(view: ViewData, nodeIndex: number, values: an
return checkAndUpdateElementDynamic(view, nodeDef, values); return checkAndUpdateElementDynamic(view, nodeDef, values);
case NodeType.Text: case NodeType.Text:
return checkAndUpdateTextDynamic(view, nodeDef, values); return checkAndUpdateTextDynamic(view, nodeDef, values);
case NodeType.Provider: case NodeType.Directive:
return checkAndUpdateProviderDynamic(view, nodeDef, values); return checkAndUpdateDirectiveDynamic(view, nodeDef, values);
case NodeType.PureExpression: case NodeType.PureExpression:
return checkAndUpdatePureExpressionDynamic(view, nodeDef, values); return checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
} }
@ -462,14 +480,14 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
} }
export function destroyView(view: ViewData) { export function destroyView(view: ViewData) {
execEmbeddedViewsAction(view, ViewAction.Destroy);
execComponentViewsAction(view, ViewAction.Destroy);
callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy); callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy);
if (view.disposables) { if (view.disposables) {
for (let i = 0; i < view.disposables.length; i++) { for (let i = 0; i < view.disposables.length; i++) {
view.disposables[i](); view.disposables[i]();
} }
} }
execComponentViewsAction(view, ViewAction.Destroy);
execEmbeddedViewsAction(view, ViewAction.Destroy);
view.state |= ViewState.Destroyed; view.state |= ViewState.Destroyed;
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {USE_VIEW_ENGINE} from '@angular/compiler/src/config';
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry'; import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings'; import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core'; import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
@ -19,6 +20,19 @@ import {MockSchemaRegistry} from '../../../compiler/testing/index';
import {EventEmitter} from '../../src/facade/async'; import {EventEmitter} from '../../src/facade/async';
export function main() { export function main() {
describe('Current compiler', () => { createTests({viewEngine: false}); });
describe('View Engine compiler', () => {
beforeEach(() => {
TestBed.configureCompiler(
{useJit: true, providers: [{provide: USE_VIEW_ENGINE, useValue: true}]});
});
createTests({viewEngine: true});
});
}
function createTests({viewEngine}: {viewEngine: boolean}) {
let elSchema: MockSchemaRegistry; let elSchema: MockSchemaRegistry;
let renderLog: RenderLog; let renderLog: RenderLog;
let directiveLog: DirectiveLog; let directiveLog: DirectiveLog;
@ -1080,7 +1094,8 @@ export function main() {
ctx.destroy(); ctx.destroy();
expect(directiveLog.filter(['ngOnDestroy'])).toEqual([ // We don't care about the exact order in this test.
expect(directiveLog.filter(['ngOnDestroy']).sort()).toEqual([
'dir.ngOnDestroy', 'injectable.ngOnDestroy' 'dir.ngOnDestroy', 'injectable.ngOnDestroy'
]); ]);
})); }));
@ -1092,8 +1107,9 @@ export function main() {
it('should throw when a record gets changed after it has been checked', fakeAsync(() => { it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData); const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
ctx.componentInstance.a = 1; ctx.componentInstance.a = 1;
expect(() => ctx.checkNoChanges()) expect(() => ctx.checkNoChanges())
.toThrowError(/:0:5[\s\S]*Expression has changed after it was checked./g); .toThrowError(/Expression has changed after it was checked./g);
})); }));
it('should warn when the view has been created in a cd hook', fakeAsync(() => { it('should warn when the view has been created in a cd hook', fakeAsync(() => {
@ -1216,6 +1232,8 @@ export function main() {
expect(renderLog.loggedValues).toEqual(['Tom']); expect(renderLog.loggedValues).toEqual(['Tom']);
}); });
// TODO(tbosch): ViewQueries don't work yet with the view engine...
viewEngine ||
it('should recurse into nested view containers even if there are no bindings in the component view', it('should recurse into nested view containers even if there are no bindings in the component view',
() => { () => {
@Component({template: '<template #vc>{{name}}</template>'}) @Component({template: '<template #vc>{{name}}</template>'})

View File

@ -265,7 +265,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}); });
describe('pipes', () => { describe('pipes', () => {
viewEngine || it('should support pipes in bindings', () => { it('should support pipes in bindings', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>'; const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -545,7 +545,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}); });
describe('variables', () => { describe('variables', () => {
viewEngine || it('should allow to use variables in a for loop', () => { it('should allow to use variables in a for loop', () => {
TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]}); TestBed.configureTestingModule({declarations: [MyComp, ChildCompNoTemplate]});
const template = const template =
'<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>'; '<template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</template>';
@ -670,7 +670,6 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
}); });
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
viewEngine ||
it('should be checked when an async pipe requests a check', fakeAsync(() => { it('should be checked when an async pipe requests a check', fakeAsync(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]});
@ -680,8 +679,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
tick(); tick();
const cmp: PushCmpWithAsyncPipe = const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references['cmp'];
fixture.debugElement.children[0].references['cmp'];
fixture.detectChanges(); fixture.detectChanges();
expect(cmp.numberOfChecks).toEqual(1); expect(cmp.numberOfChecks).toEqual(1);
@ -897,19 +895,17 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
expect(tc.nativeElement.id).toEqual('newId'); expect(tc.nativeElement.id).toEqual('newId');
}); });
viewEngine ||
it('should not use template variables for expressions in hostProperties', () => { it('should not use template variables for expressions in hostProperties', () => {
@Directive( @Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
{selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
class DirectiveWithHostProps { class DirectiveWithHostProps {
id = 'one'; id = 'one';
} }
const fixture = const fixture =
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]})
.overrideComponent(MyComp, { .overrideComponent(
set: {template: `<div *ngFor="let id of ['forId']" host-properties></div>`} MyComp,
}) {set: {template: `<div *ngFor="let id of ['forId']" host-properties></div>`}})
.createComponent(MyComp); .createComponent(MyComp);
fixture.detectChanges(); fixture.detectChanges();
@ -930,8 +926,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea
.toThrowError(/Host binding expression cannot contain pipes/); .toThrowError(/Host binding expression cannot contain pipes/);
}); });
// TODO: literal array it('should not use template variables for expressions in hostListeners', () => {
viewEngine || it('should not use template variables for expressions in hostListeners', () => {
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}}) @Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}})
class DirectiveWithHostListener { class DirectiveWithHostListener {
id = 'one'; id = 'one';

View File

@ -16,8 +16,9 @@ import {createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe(`View Anchor`, () => { describe(`View Anchor`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
return viewDef(ViewFlags.None, nodes, update, handleEvent); handleEvent?: ViewHandleEventFn): ViewDefinition {
return viewDef(ViewFlags.None, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes( function createAndGetRootNodes(

View File

@ -16,9 +16,9 @@ import {createRootView, isBrowser, removeNodes} from './helper';
export function main() { export function main() {
describe(`Component Views`, () => { describe(`Component Views`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
@ -119,7 +119,7 @@ export function main() {
directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef( directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
], update ], null, update
)), )),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asProviderData(view, 1).componentView;
@ -194,7 +194,7 @@ export function main() {
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']), elementDef(NodeFlags.None, null, null, 0, 'span', null, null, ['click']),
], ],
update, null, ViewFlags.OnPush)), update, null, null, ViewFlags.OnPush)),
], ],
(check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); })); (check, view) => { check(view, 1, ArgumentType.Inline, compInputValue); }));
@ -246,7 +246,7 @@ export function main() {
[ [
elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]),
], ],
update)), null, update)),
])); ]));
const compView = asProviderData(view, 1).componentView; const compView = asProviderData(view, 1).componentView;

View File

@ -17,9 +17,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, re
export function main() { export function main() {
describe(`View Elements`, () => { describe(`View Elements`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes( function createAndGetRootNodes(
@ -85,7 +85,7 @@ export function main() {
[BindingType.ElementProperty, 'value', SecurityContext.NONE] [BindingType.ElementProperty, 'value', SecurityContext.NONE]
]), ]),
], ],
(check, view) => { null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
})); }));
@ -112,7 +112,7 @@ export function main() {
[BindingType.ElementAttribute, 'a2', SecurityContext.NONE] [BindingType.ElementAttribute, 'a2', SecurityContext.NONE]
]), ]),
], ],
(check, view) => { null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']);
})); }));
@ -159,7 +159,7 @@ export function main() {
[BindingType.ElementStyle, 'color', null] [BindingType.ElementStyle, 'color', null]
]), ]),
], ],
(check, view) => { null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']); checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [10, 'red']);
})); }));
@ -172,42 +172,6 @@ export function main() {
}); });
}); });
describe('general binding behavior', () => {
ARG_TYPE_VALUES.forEach((inlineDynamic) => {
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(
NodeFlags.None, null, null, 0, 'input', null,
[
[BindingType.ElementProperty, 'someProp', SecurityContext.NONE],
]),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]);
}));
const setterSpy = jasmine.createSpy('set');
Object.defineProperty(rootNodes[0], 'someProp', {set: setterSpy});
bindingValue = 'v1';
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
setterSpy.calls.reset();
Services.checkAndUpdateView(view);
expect(setterSpy).not.toHaveBeenCalled();
setterSpy.calls.reset();
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
});
});
});
if (isBrowser()) { if (isBrowser()) {
describe('listen to DOM events', () => { describe('listen to DOM events', () => {
function createAndAttachAndGetRootNodes(viewDef: ViewDefinition): function createAndAttachAndGetRootNodes(viewDef: ViewDefinition):
@ -228,7 +192,7 @@ export function main() {
spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough(); spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null, [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
handleEventSpy)); null, handleEventSpy));
rootNodes[0].click(); rootNodes[0].click();
@ -252,7 +216,7 @@ export function main() {
[elementDef( [elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, NodeFlags.None, null, null, 0, 'button', null, null,
[['window', 'windowClick']])], [['window', 'windowClick']])],
null, handleEventSpy)); null, null, handleEventSpy));
expect(addListenerSpy).toHaveBeenCalled(); expect(addListenerSpy).toHaveBeenCalled();
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick'); expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick');
@ -278,7 +242,7 @@ export function main() {
[elementDef( [elementDef(
NodeFlags.None, null, null, 0, 'button', null, null, NodeFlags.None, null, null, 0, 'button', null, null,
[['document', 'documentClick']])], [['document', 'documentClick']])],
null, handleEventSpy)); null, null, handleEventSpy));
expect(addListenerSpy).toHaveBeenCalled(); expect(addListenerSpy).toHaveBeenCalled();
expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick'); expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick');
@ -302,7 +266,7 @@ export function main() {
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null, [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
(view, index, eventName, event) => { null, (view, index, eventName, event) => {
preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough(); preventDefaultSpy = spyOn(event, 'preventDefault').and.callThrough();
return eventHandlerResult; return eventHandlerResult;
})); }));
@ -328,7 +292,7 @@ export function main() {
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef(
[elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null, [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null,
() => { throw new Error('Test'); })); null, () => { throw new Error('Test'); }));
let err: any; let err: any;
try { try {

View File

@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe(`Embedded Views`, () => { describe(`Embedded Views`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {

View File

@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe(`View NgContent`, () => { describe(`View NgContent`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {

View File

@ -17,10 +17,11 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr
export function main() { export function main() {
describe(`View Providers`, () => { describe(`View Providers`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef( return viewDef(
viewFlags, nodes, update, handleEvent, 'someCompId', ViewEncapsulation.None, []); viewFlags, nodes, updateDirectives, updateRenderer, handleEvent, 'someCompId',
ViewEncapsulation.None, []);
} }
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {
@ -59,7 +60,8 @@ export function main() {
createAndGetRootNodes(compViewDef([ createAndGetRootNodes(compViewDef([
elementDef(NodeFlags.None, null, null, 2, 'span'), elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.LazyProvider, null, 0, LazyService, []), providerDef(
NodeFlags.LazyProvider, null, ProviderType.Class, LazyService, LazyService, []),
directiveDef(NodeFlags.None, null, 0, SomeService, [Injector]) directiveDef(NodeFlags.None, null, 0, SomeService, [Injector])
])); ]));
@ -330,37 +332,6 @@ export function main() {
expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1'); expect(getDOM().getAttribute(el, 'ng-reflect-a')).toBe('v1');
}); });
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
let setterSpy = jasmine.createSpy('set');
class SomeService {
set a(value: any) { setterSpy(value); }
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
directiveDef(NodeFlags.None, null, 0, SomeService, [], {a: [0, 'a']})
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
}));
bindingValue = 'v1';
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
setterSpy.calls.reset();
Services.checkAndUpdateView(view);
expect(setterSpy).not.toHaveBeenCalled();
setterSpy.calls.reset();
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
});
}); });
}); });
@ -388,7 +359,7 @@ export function main() {
directiveDef( directiveDef(
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'}) NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
], ],
null, handleEvent)); null, null, handleEvent));
emitter.emit('someEventInstance'); emitter.emit('someEventInstance');
expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance'); expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance');
@ -410,7 +381,7 @@ export function main() {
directiveDef( directiveDef(
NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'}) NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'})
], ],
null, () => { throw new Error('Test'); })); null, null, () => { throw new Error('Test'); }));
let err: any; let err: any;
try { try {

View File

@ -7,7 +7,7 @@
*/ */
import {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core'; import {Injector, PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue} from '@angular/core';
import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index'; import {ArgumentType, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asProviderData, asPureExpressionData, directiveDef, elementDef, nodeValue, pipeDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {inject} from '@angular/core/testing'; import {inject} from '@angular/core/testing';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper'; import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper';
@ -15,9 +15,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helpe
export function main() { export function main() {
describe(`View Pure Expressions`, () => { describe(`View Pure Expressions`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
@ -64,32 +64,6 @@ export function main() {
expect(arr1).toEqual([3, 2]); expect(arr1).toEqual([3, 2]);
}); });
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
pureArrayDef(1),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
}));
const exprData = asPureExpressionData(view, 1);
bindingValue = 'v1';
Services.checkAndUpdateView(view);
const v1Arr = exprData.value;
expect(v1Arr).toEqual(['v1']);
Services.checkAndUpdateView(view);
expect(exprData.value).toBe(v1Arr);
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(exprData.value).not.toBe(v1Arr);
expect(exprData.value).toEqual(['v1']);
});
}); });
}); });
@ -127,32 +101,6 @@ export function main() {
expect(obj1).toEqual({a: 3, b: 2}); expect(obj1).toEqual({a: 3, b: 2});
}); });
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 1, 'span'),
pureObjectDef(['a']),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 1, inlineDynamic, [bindingValue]);
}));
const exprData = asPureExpressionData(view, 1);
bindingValue = 'v1';
Services.checkAndUpdateView(view);
const v1Obj = exprData.value;
expect(v1Obj).toEqual({'a': 'v1'});
Services.checkAndUpdateView(view);
expect(exprData.value).toBe(v1Obj);
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(exprData.value).not.toBe(v1Obj);
expect(exprData.value).toEqual({'a': 'v1'});
});
}); });
}); });
@ -168,11 +116,12 @@ export function main() {
const {view, rootNodes} = createAndGetRootNodes(compViewDef( const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[ [
elementDef(NodeFlags.None, null, null, 3, 'span'), elementDef(NodeFlags.None, null, null, 3, 'span'),
directiveDef(NodeFlags.None, null, 0, SomePipe, []), purePipeDef(SomePipe, 2), pipeDef(NodeFlags.None, SomePipe, []), purePipeDef(2),
directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}) directiveDef(NodeFlags.None, null, 0, Service, [], {data: [0, 'data']})
], ],
(check, view) => { (check, view) => {
const pureValue = checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, values); const pureValue = checkNodeInlineOrDynamic(
check, view, 2, inlineDynamic, [nodeValue(view, 1)].concat(values));
checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]); checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]);
})); }));
const service = asProviderData(view, 3).instance; const service = asProviderData(view, 3).instance;
@ -194,36 +143,6 @@ export function main() {
expect(obj1).toEqual([13, 22]); expect(obj1).toEqual([13, 22]);
}); });
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
let transformSpy = jasmine.createSpy('transform');
class SomePipe implements PipeTransform {
transform = transformSpy;
}
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
elementDef(NodeFlags.None, null, null, 2, 'span'),
directiveDef(NodeFlags.None, null, 0, SomePipe, []),
purePipeDef(SomePipe, 1),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 2, inlineDynamic, [bindingValue]);
}));
bindingValue = 'v1';
Services.checkAndUpdateView(view);
expect(transformSpy).toHaveBeenCalledWith('v1');
transformSpy.calls.reset();
Services.checkAndUpdateView(view);
expect(transformSpy).not.toHaveBeenCalled();
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(transformSpy).toHaveBeenCalledWith('v1');
});
}); });
}); });
}); });

View File

@ -17,9 +17,9 @@ import {createRootView} from './helper';
export function main() { export function main() {
describe(`Query Views`, () => { describe(`Query Views`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory {

View File

@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper';
export function main() { export function main() {
describe('View Services', () => { describe('View Services', () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes( function createAndGetRootNodes(

View File

@ -16,9 +16,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr
export function main() { export function main() {
describe(`View Text`, () => { describe(`View Text`, () => {
function compViewDef( function compViewDef(
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn,
viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition {
return viewDef(viewFlags, nodes, update, handleEvent); return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent);
} }
function createAndGetRootNodes( function createAndGetRootNodes(
@ -67,7 +67,7 @@ export function main() {
[ [
textDef(null, ['0', '1', '2']), textDef(null, ['0', '1', '2']),
], ],
(check, view) => { null, (check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']); checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']);
})); }));
@ -77,41 +77,6 @@ export function main() {
expect(getDOM().getText(rootNodes[0])).toBe('0a1b2'); expect(getDOM().getText(rootNodes[0])).toBe('0a1b2');
}); });
if (isBrowser()) {
it(`should unwrap values with ${ArgumentType[inlineDynamic]}`, () => {
let bindingValue: any;
const setterSpy = jasmine.createSpy('set');
class FakeTextNode {
set nodeValue(value: any) { setterSpy(value); }
}
spyOn(document, 'createTextNode').and.returnValue(new FakeTextNode());
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
[
textDef(null, ['', '']),
],
(check, view) => {
checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, [bindingValue]);
}));
Object.defineProperty(rootNodes[0], 'nodeValue', {set: setterSpy});
bindingValue = 'v1';
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
setterSpy.calls.reset();
Services.checkAndUpdateView(view);
expect(setterSpy).not.toHaveBeenCalled();
setterSpy.calls.reset();
bindingValue = WrappedValue.wrap('v1');
Services.checkAndUpdateView(view);
expect(setterSpy).toHaveBeenCalledWith('v1');
});
}
}); });
}); });

View File

@ -75,10 +75,13 @@ function TreeComponent_0(): ViewDefinition {
], ],
(check, view) => { (check, view) => {
const cmp = view.component; const cmp = view.component;
check(view, 0, ArgumentType.Inline, cmp.bgColor);
check(view, 1, ArgumentType.Inline, cmp.data.value);
check(view, 3, ArgumentType.Inline, cmp.data.left != null); check(view, 3, ArgumentType.Inline, cmp.data.left != null);
check(view, 5, ArgumentType.Inline, cmp.data.right != null); check(view, 5, ArgumentType.Inline, cmp.data.right != null);
},
(check, view) => {
const cmp = view.component;
check(view, 0, ArgumentType.Inline, cmp.bgColor);
check(view, 1, ArgumentType.Inline, cmp.data.value);
}); });
} }