From e4e9dbe33d6d94e3688e98ad340d6fc0ab5d98c1 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 9 Feb 2017 14:59:57 -0800 Subject: [PATCH] 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 --- modules/@angular/compiler/src/aot/compiler.ts | 4 +- .../src/compiler_util/expression_converter.ts | 276 ++++++++++------ .../compiler/src/compiler_util/render_util.ts | 6 +- .../src/directive_wrapper_compiler.ts | 7 +- modules/@angular/compiler/src/identifiers.ts | 25 ++ modules/@angular/compiler/src/jit/compiler.ts | 4 +- .../src/template_parser/binding_parser.ts | 8 +- .../src/template_parser/template_parser.ts | 80 +++-- .../src/view_compiler/compile_view.ts | 4 +- .../src/view_compiler/event_binder.ts | 4 +- .../src/view_compiler/property_binder.ts | 8 +- .../src/view_compiler/view_builder.ts | 4 +- .../src/view_compiler_next/view_compiler.ts | 301 +++++++++++++----- .../template_parser/template_parser_spec.ts | 11 +- modules/@angular/core/src/view/element.ts | 4 +- modules/@angular/core/src/view/errors.ts | 1 - modules/@angular/core/src/view/index.ts | 4 +- modules/@angular/core/src/view/provider.ts | 180 ++++++----- .../@angular/core/src/view/pure_expression.ts | 70 ++-- modules/@angular/core/src/view/query.ts | 16 +- modules/@angular/core/src/view/refs.ts | 8 +- modules/@angular/core/src/view/services.ts | 102 ++++-- modules/@angular/core/src/view/text.ts | 5 +- modules/@angular/core/src/view/types.ts | 32 +- modules/@angular/core/src/view/util.ts | 54 ++-- modules/@angular/core/src/view/view.ts | 68 ++-- .../change_detection_integration_spec.ts | 56 ++-- .../core/test/linker/integration_spec.ts | 79 +++-- .../@angular/core/test/view/anchor_spec.ts | 5 +- .../core/test/view/component_view_spec.ts | 12 +- .../@angular/core/test/view/element_spec.ts | 58 +--- .../core/test/view/embedded_view_spec.ts | 6 +- .../core/test/view/ng_content_spec.ts | 6 +- .../@angular/core/test/view/provider_spec.ts | 45 +-- .../core/test/view/pure_expression_spec.ts | 95 +----- modules/@angular/core/test/view/query_spec.ts | 6 +- .../@angular/core/test/view/services_spec.ts | 6 +- modules/@angular/core/test/view/text_spec.ts | 43 +-- modules/benchmarks/src/tree/ng2_next/tree.ts | 7 +- 39 files changed, 942 insertions(+), 768 deletions(-) diff --git a/modules/@angular/compiler/src/aot/compiler.ts b/modules/@angular/compiler/src/aot/compiler.ts index 8da7da80a4..89ed6f4de6 100644 --- a/modules/@angular/compiler/src/aot/compiler.ts +++ b/modules/@angular/compiler/src/aot/compiler.ts @@ -205,14 +205,14 @@ export class AotCompiler { const pipes = ngModule.transitiveModule.pipes.map( 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, identifierName(compMeta.type)); const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]); const compiledAnimations = this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations); const viewResult = this._viewCompiler.compileComponent( - compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations); + compMeta, parsedTemplate, stylesExpr, usedPipes, compiledAnimations); if (componentStyles) { targetStatements.push( ..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix)); diff --git a/modules/@angular/compiler/src/compiler_util/expression_converter.ts b/modules/@angular/compiler/src/compiler_util/expression_converter.ts index 099b1afe5f..1d45cb95a6 100644 --- a/modules/@angular/compiler/src/compiler_util/expression_converter.ts +++ b/modules/@angular/compiler/src/compiler_util/expression_converter.ts @@ -17,58 +17,9 @@ import {createPureProxy} from './identifier_util'; 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 ConvertPropertyBindingResult { - 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 interface LocalResolver { getLocal(name: string): o.Expression; } export class ConvertActionBindingResult { 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). */ export function convertActionBinding( - builder: ClassBuilder, nameResolver: NameResolver, implicitReceiver: o.Expression, - action: cdAst.AST, bindingId: string): ConvertActionBindingResult { - if (!nameResolver) { - nameResolver = new DefaultNameResolver(); + localResolver: LocalResolver, implicitReceiver: o.Expression, action: cdAst.AST, + bindingId: string): ConvertActionBindingResult { + if (!localResolver) { + localResolver = new DefaultLocalResolver(); } - const visitor = - new _AstToIrVisitor(builder, nameResolver, implicitReceiver, null, bindingId, true); + const actionWithoutBuiltins = convertPropertyBindingBuiltins( + { + 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[] = []; - flattenStatements(action.visit(visitor, _Mode.Statement), actionStmts); + flattenStatements(actionWithoutBuiltins.visit(visitor, _Mode.Statement), actionStmts); prependTemporaryDecls(visitor.temporaryCount, bindingId, actionStmts); const lastIndex = actionStmts.length - 1; let preventDefaultVar: o.ReadVarExpr = null; @@ -106,11 +73,105 @@ export function convertActionBinding( 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` / * `convertPropertyBinding` */ -export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] { +export function legacyCreateSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.Statement[] { const unwrapperStmts: o.Statement[] = []; const readVars = o.findReadVarNames(stmts); if (readVars.has(VAL_UNWRAPPER_VAR.name)) { @@ -122,6 +183,11 @@ export function createSharedBindingVariablesIfNeeded(stmts: o.Statement[]): o.St 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 { 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 { private _nodeMap = new Map(); private _resultMap = new Map(); private _currentTemporary: number = 0; - public needsValueUnwrapper: boolean = false; public temporaryCount: number = 0; constructor( - private _builder: ClassBuilder, private _nameResolver: NameResolver, - private _implicitReceiver: o.Expression, private _valueUnwrapper: o.ReadVarExpr, - private bindingId: string, private isAction: boolean) {} + private _localResolver: LocalResolver, private _implicitReceiver: o.Expression, + private bindingId: string) {} visitBinary(ast: cdAst.Binary, mode: _Mode): any { let op: o.BinaryOperator; @@ -246,20 +329,19 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { - const input = this.visit(ast.exp, _Mode.Expression); - const args = this.visitAll(ast.args, _Mode.Expression); - 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])); + throw new Error( + `Illegal state: Pipes should have been converted into functions. Pipe: ${ast.name}`); } visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { - return convertToStatementIfNeeded( - mode, - this.visit(ast.target, _Mode.Expression).callFn(this.visitAll(ast.args, _Mode.Expression))); + const convertedArgs = this.visitAll(ast.args, _Mode.Expression); + let fnResult: o.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 { @@ -301,32 +383,18 @@ class _AstToIrVisitor implements cdAst.AstVisitor { } visitLiteralArray(ast: cdAst.LiteralArray, mode: _Mode): any { - const parts = this.visitAll(ast.expressions, mode); - const literalArr = - this.isAction ? o.literalArr(parts) : createCachedLiteralArray(this._builder, parts); - return convertToStatementIfNeeded(mode, literalArr); + throw new Error(`Illegal State: literal arrays should have been converted into functions`); } visitLiteralMap(ast: cdAst.LiteralMap, mode: _Mode): any { - const parts: any[] = []; - 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); + throw new Error(`Illegal State: literal maps should have been converted into functions`); } visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { return convertToStatementIfNeeded(mode, o.literal(ast.value)); } - private _getLocal(name: string): o.Expression { - if (this.isAction && name == EventHandlerVars.event.name) { - return EventHandlerVars.event; - } - return this._nameResolver.getLocal(name); - } + private _getLocal(name: string): o.Expression { return this._localResolver.getLocal(name); } visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { 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) { return o.importExpr(createIdentifier(Identifiers.EMPTY_ARRAY)); } @@ -601,7 +670,7 @@ function createCachedLiteralArray(builder: ClassBuilder, values: o.Expression[]) return proxyExpr.callFn(values); } -function createCachedLiteralMap( +function legacyCreateCachedLiteralMap( builder: ClassBuilder, entries: [string, o.Expression][]): o.Expression { if (entries.length === 0) { return o.importExpr(createIdentifier(Identifiers.EMPTY_MAP)); @@ -624,10 +693,23 @@ function createCachedLiteralMap( 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; } - 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 { @@ -646,3 +728,9 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression { } return null; } + +class BuiltinFunctionCall extends cdAst.FunctionCall { + constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) { + super(span, null, args); + } +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/compiler_util/render_util.ts b/modules/@angular/compiler/src/compiler_util/render_util.ts index 4a51ac6c19..f6be8950a4 100644 --- a/modules/@angular/compiler/src/compiler_util/render_util.ts +++ b/modules/@angular/compiler/src/compiler_util/render_util.ts @@ -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 {isFirstViewCheck} from './binding_util'; -import {ConvertPropertyBindingResult} from './expression_converter'; +import {LegacyConvertPropertyBindingResult} from './expression_converter'; import {createEnumExpression} from './identifier_util'; export function createCheckRenderBindingStmt( view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst, - oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult, + oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult, securityContextExpression?: o.Expression): o.Statement[] { const checkStmts: o.Statement[] = [...evalResult.stmts]; const securityContext = calcSecurityContext(boundProp, securityContextExpression); @@ -84,7 +84,7 @@ function calcSecurityContext( export function createCheckAnimationBindingStmts( view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst, boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression, - oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) { + oldValue: o.ReadPropExpr, evalResult: LegacyConvertPropertyBindingResult) { const detachStmts: o.Statement[] = []; const updateStmts: o.Statement[] = []; diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index 5ee6a9ec4c..fd53c161c3 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -8,7 +8,7 @@ import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata'; 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 {CompilerConfig} from './config'; import {Parser} from './expression_parser/parser'; @@ -253,7 +253,7 @@ function addCheckHostMethod( ]; hostProps.forEach((hostProp, hostPropIdx) => { const field = createCheckBindingField(builder); - const evalResult = convertPropertyBinding( + const evalResult = legacyConvertPropertyBinding( builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostProp.value, field.bindingId); if (!evalResult) { return; @@ -285,8 +285,7 @@ function addHandleEventMethod(hostListeners: BoundEventAst[], builder: Directive const actionStmts: o.Statement[] = [resultVar.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE)]; hostListeners.forEach((hostListener, eventIdx) => { const evalResult = convertActionBinding( - builder, null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, - `sub_${eventIdx}`); + null, o.THIS_EXPR.prop(CONTEXT_FIELD_NAME), hostListener.handler, `sub_${eventIdx}`); const trueStmts = evalResult.stmts; if (evalResult.allowDefault) { trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); diff --git a/modules/@angular/compiler/src/identifiers.ts b/modules/@angular/compiler/src/identifiers.ts index a8d314682d..6047698c9b 100644 --- a/modules/@angular/compiler/src/identifiers.ts +++ b/modules/@angular/compiler/src/identifiers.ts @@ -400,11 +400,36 @@ export class Identifiers { moduleUrl: VIEW_ENGINE_MODULE_URL, 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 = { name: 'nodeValue', moduleUrl: VIEW_ENGINE_MODULE_URL, 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 { diff --git a/modules/@angular/compiler/src/jit/compiler.ts b/modules/@angular/compiler/src/jit/compiler.ts index 6f497af5ac..4e443c97db 100644 --- a/modules/@angular/compiler/src/jit/compiler.ts +++ b/modules/@angular/compiler/src/jit/compiler.ts @@ -278,14 +278,14 @@ export class JitCompiler implements Compiler { template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference)); const pipes = template.ngModule.transitiveModule.pipes.map( 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, identifierName(compMeta.type)); const compiledAnimations = this._animationCompiler.compile(identifierName(compMeta.type), parsedAnimations); const compileResult = this._viewCompiler.compileComponent( compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar), - pipes, compiledAnimations); + usedPipes, compiledAnimations); const statements = stylesCompileResult.componentStylesheet.statements .concat(...compiledAnimations.map(ca => ca.statements)) .concat(compileResult.statements); diff --git a/modules/@angular/compiler/src/template_parser/binding_parser.ts b/modules/@angular/compiler/src/template_parser/binding_parser.ts index 9756c19ccb..f45151a3d8 100644 --- a/modules/@angular/compiler/src/template_parser/binding_parser.ts +++ b/modules/@angular/compiler/src/template_parser/binding_parser.ts @@ -51,6 +51,7 @@ export class BoundProperty { */ export class BindingParser { pipesByName: Map = new Map(); + private _usedPipes: Map = new Map(); constructor( private _exprParser: Parser, private _interpolationConfig: InterpolationConfig, @@ -59,6 +60,8 @@ export class BindingParser { pipes.forEach(pipe => this.pipesByName.set(pipe.name, pipe)); } + getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); } + createDirectiveHostPropertyAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan): BoundElementPropertyAst[] { if (dirMeta.hostProperties) { @@ -377,11 +380,14 @@ export class BindingParser { const collector = new PipeCollector(); ast.visit(collector); collector.pipes.forEach((ast, pipeName) => { - if (!this.pipesByName.has(pipeName)) { + const pipeMeta = this.pipesByName.get(pipeName); + if (!pipeMeta) { this._reportError( `The pipe '${pipeName}' could not be found`, new ParseSourceSpan( sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end))); + } else { + this._usedPipes.set(pipeName, pipeMeta); } }); } diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 8a66913694..ea9e94ea03 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -7,7 +7,9 @@ */ import {Inject, InjectionToken, Optional, SchemaMetadata} from '@angular/core'; + 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 {isPresent} from '../facade/lang'; import {I18NHtmlParser} from '../i18n/i18n_html_parser'; @@ -25,10 +27,12 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {CssSelector, SelectorMatcher} from '../selector'; import {isStyleUrlResolvable} from '../style_url_resolver'; import {syntaxError} from '../util'; + 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 {PreparsedElementType, preparseElement} from './template_preparser'; + // Group 1 = "bind-" // Group 2 = "let-" // Group 3 = "ref-/#" @@ -76,7 +80,9 @@ export class TemplateParseError extends ParseError { } export class TemplateParseResult { - constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {} + constructor( + public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[], + public errors?: ParseError[]) {} } @CompilerInjectable() @@ -88,7 +94,8 @@ export class TemplateParser { parse( 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 warnings = result.errors.filter(error => error.level === ParseErrorLevel.WARNING); const errors = result.errors.filter(error => error.level === ParseErrorLevel.FATAL); @@ -102,7 +109,7 @@ export class TemplateParser { throw syntaxError(`Template parse errors:\n${errorString}`); } - return result.templateAst; + return {template: result.templateAst, pipes: result.usedPipes}; } tryParse( @@ -121,6 +128,7 @@ export class TemplateParser { templateUrl: string): TemplateParseResult { let result: TemplateAst[]; const errors = htmlAstWithErrors.errors; + const usedPipes: CompilePipeSummary[] = []; if (htmlAstWithErrors.rootNodes.length > 0) { const uniqDirectives = removeSummaryDuplicates(directives); const uniqPipes = removeSummaryDuplicates(pipes); @@ -140,13 +148,14 @@ export class TemplateParser { errors); result = html.visitAll(parseVisitor, htmlAstWithErrors.rootNodes, EMPTY_ELEMENT_CONTEXT); errors.push(...providerViewContext.errors); + usedPipes.push(...bindingParser.getUsedPipes()); } else { result = []; } this._assertNoReferenceDuplicationOnTemplate(result, errors); if (errors.length > 0) { - return new TemplateParseResult(result, errors); + return new TemplateParseResult(result, usedPipes, errors); } if (this.transforms) { @@ -154,7 +163,7 @@ export class TemplateParser { (transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); }); } - return new TemplateParseResult(result, errors); + return new TemplateParseResult(result, usedPipes, errors); } expandHtml(htmlAstWithErrors: ParseTreeResult, forced: boolean = false): ParseTreeResult { @@ -303,11 +312,12 @@ class TemplateParseVisitor implements html.Visitor { const {directives: directiveMetas, matchElement} = this._parseDirectives(this.selectorMatcher, elementCssSelector); const references: ReferenceAst[] = []; + const boundDirectivePropNames = new Set(); const directiveAsts = this._createDirectiveAsts( isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps, - elementOrDirectiveRefs, element.sourceSpan, references); - const elementProps: BoundElementPropertyAst[] = - this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts); + elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames); + const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts( + element.name, elementOrDirectiveProps, boundDirectivePropNames); const isViewRoot = parent.isTemplateElement || hasInlineTemplates; const providerContext = new ProviderElementContext( @@ -372,11 +382,12 @@ class TemplateParseVisitor implements html.Visitor { createElementCssSelector(TEMPLATE_ELEMENT, templateMatchableAttrs); const {directives: templateDirectiveMetas} = this._parseDirectives(this.selectorMatcher, templateCssSelector); + const templateBoundDirectivePropNames = new Set(); const templateDirectiveAsts = this._createDirectiveAsts( true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [], - element.sourceSpan, []); + element.sourceSpan, [], templateBoundDirectivePropNames); const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts( - element.name, templateElementOrDirectiveProps, templateDirectiveAsts); + element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames); this._assertNoComponentsNorElementBindingsOnTemplate( templateDirectiveAsts, templateElementProps, element.sourceSpan); const templateProviderContext = new ProviderElementContext( @@ -544,7 +555,8 @@ class TemplateParseVisitor implements html.Visitor { private _createDirectiveAsts( isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[], props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[], - elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[]): DirectiveAst[] { + elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[], + targetBoundDirectivePropNames: Set): DirectiveAst[] { const matchedReferences = new Set(); let component: CompileDirectiveSummary = null; @@ -557,13 +569,14 @@ class TemplateParseVisitor implements html.Visitor { component = directive; } const directiveProperties: BoundDirectivePropertyAst[] = []; - const hostProperties = + let hostProperties = this._bindingParser.createDirectiveHostPropertyAsts(directive, sourceSpan); // Note: We need to check the host properties here as well, // 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); - this._createDirectivePropertyAsts(directive.inputs, props, directiveProperties); + this._createDirectivePropertyAsts( + directive.inputs, props, directiveProperties, targetBoundDirectivePropNames); elementOrDirectiveRefs.forEach((elOrDirRef) => { if ((elOrDirRef.value.length === 0 && directive.isComponent) || (directive.exportAs == elOrDirRef.value)) { @@ -596,7 +609,8 @@ class TemplateParseVisitor implements html.Visitor { private _createDirectivePropertyAsts( directiveProperties: {[key: string]: string}, boundProps: BoundProperty[], - targetBoundDirectiveProps: BoundDirectivePropertyAst[]) { + targetBoundDirectiveProps: BoundDirectivePropertyAst[], + targetBoundDirectivePropNames: Set) { if (directiveProperties) { const boundPropsByName = new Map(); boundProps.forEach(boundProp => { @@ -613,8 +627,11 @@ class TemplateParseVisitor implements html.Visitor { // Bindings are optional, so this binding only needs to be set up if an expression is given. if (boundProp) { - targetBoundDirectiveProps.push(new BoundDirectivePropertyAst( - dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan)); + targetBoundDirectivePropNames.add(boundProp.name); + if (!isEmptyExpression(boundProp.expression)) { + targetBoundDirectiveProps.push(new BoundDirectivePropertyAst( + dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan)); + } } }); } @@ -622,23 +639,15 @@ class TemplateParseVisitor implements html.Visitor { private _createElementPropertyAsts( elementName: string, props: BoundProperty[], - directives: DirectiveAst[]): BoundElementPropertyAst[] { + boundDirectivePropNames: Set): BoundElementPropertyAst[] { const boundElementProps: BoundElementPropertyAst[] = []; - const boundDirectivePropsIndex = new Map(); - - directives.forEach((directive: DirectiveAst) => { - directive.inputs.forEach((prop: BoundDirectivePropertyAst) => { - boundDirectivePropsIndex.set(prop.templateName, prop); - }); - }); 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)); } }); - this._checkPropertiesInSchema(elementName, boundElementProps); - return boundElementProps; + return this._checkPropertiesInSchema(elementName, boundElementProps); } private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] { @@ -723,8 +732,11 @@ class TemplateParseVisitor implements html.Visitor { }); } - private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]) { - boundProps.forEach((boundProp) => { + private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]): + 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 && !this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) { let errorMsg = @@ -741,6 +753,7 @@ class TemplateParseVisitor implements html.Visitor { } this._reportError(errorMsg, boundProp.sourceSpan); } + return !isEmptyExpression(boundProp.value); }); } @@ -870,3 +883,10 @@ export function removeSummaryDuplicates(it return Array.from(map.values()); } + +function isEmptyExpression(ast: AST): boolean { + if (ast instanceof ASTWithSource) { + ast = ast.ast; + } + return ast instanceof EmptyExpr; +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/view_compiler/compile_view.ts b/modules/@angular/compiler/src/view_compiler/compile_view.ts index a9a920cabd..3dbcafdc3b 100644 --- a/modules/@angular/compiler/src/view_compiler/compile_view.ts +++ b/modules/@angular/compiler/src/view_compiler/compile_view.ts @@ -8,7 +8,7 @@ import {AnimationEntryCompileResult} from '../animation/animation_compiler'; 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 {isPresent} from '../facade/lang'; import * as o from '../output/output_ast'; @@ -33,7 +33,7 @@ export class CompileViewRootNode { public ngContentIndex?: number) {} } -export class CompileView implements NameResolver { +export class CompileView implements LegacyNameResolver { public viewType: ViewType; public viewQueries: Map; diff --git a/modules/@angular/compiler/src/view_compiler/event_binder.ts b/modules/@angular/compiler/src/view_compiler/event_binder.ts index 6fa54fb43c..ef3136e1fd 100644 --- a/modules/@angular/compiler/src/view_compiler/event_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/event_binder.ts @@ -102,8 +102,8 @@ function generateHandleEventMethod( }); boundEvents.forEach((renderEvent, renderEventIdx) => { const evalResult = convertActionBinding( - compileElement.view, compileElement.view, compileElement.view.componentContext, - renderEvent.handler, `sub_${renderEventIdx}`); + compileElement.view, compileElement.view.componentContext, renderEvent.handler, + `sub_${renderEventIdx}`); const trueStmts = evalResult.stmts; if (evalResult.allowDefault) { trueStmts.push(resultVar.set(evalResult.allowDefault.and(resultVar)).toStmt()); diff --git a/modules/@angular/compiler/src/view_compiler/property_binder.ts b/modules/@angular/compiler/src/view_compiler/property_binder.ts index bbe34509fb..7e3b98d7bb 100644 --- a/modules/@angular/compiler/src/view_compiler/property_binder.ts +++ b/modules/@angular/compiler/src/view_compiler/property_binder.ts @@ -9,7 +9,7 @@ import {SecurityContext} from '@angular/core'; 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 {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util'; import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler'; @@ -26,7 +26,7 @@ import {getHandleEventMethodName} from './util'; export function bindRenderText( boundText: BoundTextAst, compileNode: CompileNode, view: CompileView): void { const valueField = createCheckBindingField(view); - const evalResult = convertPropertyBinding( + const evalResult = legacyConvertPropertyBinding( view, view, view.componentContext, boundText.value, valueField.bindingId); if (!evalResult) { return null; @@ -53,7 +53,7 @@ export function bindRenderInputs( boundProps.forEach((boundProp) => { const bindingField = createCheckBindingField(view); view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileElement.nodeIndex, boundProp); - const evalResult = convertPropertyBinding( + const evalResult = legacyConvertPropertyBinding( view, view, compileElement.view.componentContext, boundProp.value, bindingField.bindingId); if (!evalResult) { return; @@ -123,7 +123,7 @@ export function bindDirectiveInputs( const bindingId = `${compileElement.nodeIndex}_${dirIndex}_${inputIdx}`; detectChangesInInputsMethod.resetDebugInfo(compileElement.nodeIndex, input); const evalResult = - convertPropertyBinding(view, view, view.componentContext, input.value, bindingId); + legacyConvertPropertyBinding(view, view, view.componentContext, input.value, bindingId); if (!evalResult) { return; } diff --git a/modules/@angular/compiler/src/view_compiler/view_builder.ts b/modules/@angular/compiler/src/view_compiler/view_builder.ts index 7cb9518a2c..607fa6f772 100644 --- a/modules/@angular/compiler/src/view_compiler/view_builder.ts +++ b/modules/@angular/compiler/src/view_compiler/view_builder.ts @@ -9,7 +9,7 @@ import {ViewEncapsulation} from '@angular/core'; 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 {isPresent} from '../facade/lang'; 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)); } - const varStmts = createSharedBindingVariablesIfNeeded(stmts); + const varStmts = legacyCreateSharedBindingVariablesIfNeeded(stmts); return varStmts.concat(stmts); } diff --git a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts index 90889f4379..d8ed0a8661 100644 --- a/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler_next/view_compiler.ts @@ -10,7 +10,7 @@ import {ChangeDetectionStrategy} from '@angular/core'; import {AnimationEntryCompileResult} from '../animation/animation_compiler'; 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 {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {Identifiers, createIdentifier, resolveIdentifier} from '../identifiers'; @@ -25,6 +25,7 @@ import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDep const CLASS_ATTR = 'class'; const STYLE_ATTR = 'style'; +const IMPLICIT_TEMPLATE_VAR = '\$implicit'; @CompilerInjectable() export class ViewCompilerNext extends ViewCompiler { @@ -35,7 +36,7 @@ export class ViewCompilerNext extends ViewCompiler { compileComponent( component: CompileDirectiveMetadata, template: TemplateAst[], styles: o.Expression, - pipes: CompilePipeSummary[], + usedPipes: CompilePipeSummary[], compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult { const compName = identifierName(component.type) + (component.isHost ? `_Host` : ''); @@ -44,7 +45,7 @@ export class ViewCompilerNext extends ViewCompiler { const viewBuilderFactory = (parent: ViewBuilder): ViewBuilder => { const embeddedViewIndex = embeddedViewCount++; const viewName = `view_${compName}_${embeddedViewIndex}`; - return new ViewBuilder(parent, viewName, viewBuilderFactory); + return new ViewBuilder(parent, viewName, usedPipes, viewBuilderFactory); }; const visitor = viewBuilderFactory(null); @@ -80,20 +81,31 @@ const NODE_INDEX_VAR = o.variable('nodeIndex'); const EVENT_NAME_VAR = o.variable('eventName'); const ALLOW_DEFAULT_VAR = o.variable(`allowDefault`); -class ViewBuilder implements TemplateAstVisitor, NameResolver { +class ViewBuilder implements TemplateAstVisitor, LocalResolver, BuiltinConverterFactory { private nodeDefs: o.Expression[] = []; + private purePipeNodeIndices: {[pipeName: string]: number} = {}; private refNodeIndices: {[refName: string]: number} = {}; private variables: VariableAst[] = []; private children: ViewBuilder[] = []; - private updateExpressions: UpdateExpression[] = []; + private updateDirectivesExpressions: UpdateExpression[] = []; + private updateRendererExpressions: UpdateExpression[] = []; private handleEventExpressions: HandleEventExpression[] = []; constructor( - private parent: ViewBuilder, public viewName: string, + private parent: ViewBuilder, public viewName: string, private usedPipes: CompilePipeSummary[], private viewBuilderFactory: ViewBuilderFactory) {} visitAll(variables: VariableAst[], astNodes: TemplateAst[], elementDepth: number) { 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}); 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, @@ -108,46 +120,16 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { const compType = o.importType(component.type); this.children.forEach((child) => { child.build(component, targetStatements); }); - const updateStmts: o.Statement[] = []; - let updateBindingCount = 0; - 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(([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 updateDirectivesFn = this._createUpdateFn(this.updateDirectivesExpressions, compType); + const updateRendererFn = this._createUpdateFn(this.updateRendererExpressions, compType); const handleEventStmts: o.Statement[] = []; let handleEventBindingCount = 0; this.handleEventExpressions.forEach(({expression, context, nodeIndex, eventName}) => { const bindingId = `${handleEventBindingCount++}`; + const nameResolver = context === COMP_VAR ? this : null; const {stmts, allowDefault} = - convertActionBinding(null, this, context, expression, bindingId); + convertActionBinding(nameResolver, context, expression, bindingId); const trueStmts = stmts; if (allowDefault) { 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( this.viewName, [], [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); 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 {} visitText(ast: TextAst, context: any): any { @@ -199,17 +207,20 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { visitBoundText(ast: BoundTextAst, context: any): any { const nodeIndex = this.nodeDefs.length; + // reserve the space in the nodeDefs array + this.nodeDefs.push(null); + const astWithSource = ast.value; const inter = astWithSource.ast; - this.updateExpressions.push({ - nodeIndex, - expressions: inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }) - }); + + this._addUpdateExpressions( + nodeIndex, inter.expressions.map((expr) => { return {context: COMP_VAR, value: expr}; }), + this.updateRendererExpressions); // 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))) - ])); + ]); } 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 childCount = this.nodeDefs.length - nodeIndex - 1; const childVisitor = this.viewBuilderFactory(this); this.children.push(childVisitor); childVisitor.visitAll(ast.variables, ast.children, context.elementDepth + 1); + const childCount = this.nodeDefs.length - nodeIndex - 1; + // anchorDef( // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // childCount: number, templateFactory?: ViewDefinitionFactory): NodeDef; @@ -243,12 +255,9 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { 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}); - }); - this.updateExpressions.push({nodeIndex, expressions: hostBindings}); + ast.inputs.forEach( + (inputAst) => { hostBindings.push({context: COMP_VAR, value: inputAst.value}); }); + this._addUpdateExpressions(nodeIndex, hostBindings, this.updateRendererExpressions); const inputDefs = elementBindingDefs(ast.inputs); ast.directives.forEach( @@ -258,6 +267,8 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { o.literal(eventName); }); + const childCount = this.nodeDefs.length - nodeIndex - 1; + // elementDef( // flags: NodeFlags, matchedQueries: [string, QueryValueType][], ngContentIndex: number, // childCount: number, name: string, fixedAttrs: {[name: string]: string} = {}, @@ -355,13 +366,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { ast.outputs.forEach( (outputAst) => { hostEvents.push({context: COMP_VAR, eventAst: outputAst}); }); hostEvents.forEach((hostEvent) => { - this.handleEventExpressions.push({ - nodeIndex, - context: hostEvent.context, - eventName: - viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name), - expression: (hostEvent.eventAst.handler).ast - }); + this._addHandleEventExpression( + nodeIndex, hostEvent.context, + viewEngine.elementEventFullName(hostEvent.eventAst.target, hostEvent.eventAst.name), + hostEvent.eventAst.handler); }); return { @@ -383,6 +391,22 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { // reserve the space in the nodeDefs array so we can add children 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} = this._visitProviderOrDirective(providerAst, queryMatches); @@ -415,11 +439,10 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { } }); if (directiveAst.inputs.length) { - this.updateExpressions.push({ - nodeIndex, - expressions: directiveAst.inputs.map( - input => { return {context: COMP_VAR, value: (input.value).ast}; }) - }); + this._addUpdateExpressions( + nodeIndex, + directiveAst.inputs.map((input) => { return {context: COMP_VAR, value: input.value}; }), + this.updateDirectivesExpressions); } const dirContextExpr = o.importExpr(createIdentifier(Identifiers.nodeValue)).callFn([ @@ -434,19 +457,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { const hostEvents = directiveAst.hostEvents.map( (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( // flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: @@ -514,10 +524,6 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { 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 { if (name == EventHandlerVars.event.name) { return EventHandlerVars.event; @@ -536,12 +542,123 @@ class ViewBuilder implements TemplateAstVisitor, NameResolver { // check variables const varAst = currBuilder.variables.find((varAst) => varAst.name === name); if (varAst) { - return currViewExpr.prop('context').prop(varAst.value); + const varValue = varAst.value || IMPLICIT_TEMPLATE_VAR; + return currViewExpr.prop('context').prop(varValue); } } 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}): any {} visitDirectiveProperty(ast: BoundDirectivePropertyAst, context: any): any {} visitReference(ast: ReferenceAst, context: any): any {} @@ -736,3 +853,19 @@ function mergeAttributeValue(attrName: string, attrValue1: string, attrValue2: s 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]); +} \ No newline at end of file diff --git a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts index 5746695918..af2b70a8c7 100644 --- a/modules/@angular/compiler/test/template_parser/template_parser_spec.ts +++ b/modules/@angular/compiler/test/template_parser/template_parser_spec.ts @@ -70,7 +70,8 @@ export function main() { if (pipes === null) { 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, template: new CompileTemplateMetadata({interpolation: ['{%', '%}']}) }); - expect(humanizeTplAst(parser.parse(component, '{%a%}', [], [], [], 'TestComp'), { - start: '{%', - end: '%}' - })).toEqual([[BoundTextAst, '{% a %}']]); + expect(humanizeTplAst( + parser.parse(component, '{%a%}', [], [], [], 'TestComp').template, + {start: '{%', end: '%}'})) + .toEqual([[BoundTextAst, '{% a %}']]); })); describe('bound properties', () => { diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index 7c518345c7..73d4f864d1 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; 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 {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack, unwrapValue} from './util'; +import {checkAndUpdateBinding, dispatchEvent, elementEventFullName, resolveViewDefinition, sliceErrorStack} from './util'; export function anchorDef( 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)) { return; } - value = unwrapValue(value); - const binding = def.bindings[bindingIdx]; const name = binding.name; const renderNode = asElementData(view, def.index).renderElement; diff --git a/modules/@angular/core/src/view/errors.ts b/modules/@angular/core/src/view/errors.ts index e4765e219d..c0b2c3dab1 100644 --- a/modules/@angular/core/src/view/errors.ts +++ b/modules/@angular/core/src/view/errors.ts @@ -31,7 +31,6 @@ export function viewDebugError(msg: string, context: DebugContext): Error { const err = new Error(msg); (err as any)[ERROR_DEBUG_CONTEXT] = context; err.stack = context.source; - context.view.state |= ViewState.Errored; return err; } diff --git a/modules/@angular/core/src/view/index.ts b/modules/@angular/core/src/view/index.ts index e1412f9c2e..403f1acb41 100644 --- a/modules/@angular/core/src/view/index.ts +++ b/modules/@angular/core/src/view/index.ts @@ -8,13 +8,13 @@ export {anchorDef, elementDef} from './element'; export {ngContentDef} from './ng_content'; -export {directiveDef, providerDef} from './provider'; +export {directiveDef, pipeDef, providerDef} from './provider'; export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression'; export {queryDef} from './query'; export {createComponentFactory} from './refs'; export {initServicesIfNeeded} from './services'; export {textDef} from './text'; -export {elementEventFullName, nodeValue, rootRenderNodes} from './util'; +export {elementEventFullName, nodeValue, rootRenderNodes, unwrapValue} from './util'; export {viewDef} from './view'; export {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index ab5f68d1f7..31f4822674 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -15,8 +15,8 @@ import * as v1renderer from '../render/api'; import {Type} from '../type'; 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 {checkAndUpdateBinding, dispatchEvent, isComponentView, tokenKey, unwrapValue, viewParentDiIndex} from './util'; +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, viewParentElIndex} from './util'; const RendererV1TokenKey = tokenKey(v1renderer.Renderer); const ElementRefTokenKey = tokenKey(ElementRef); @@ -31,45 +31,55 @@ export function directiveDef( flags: NodeFlags, matchedQueries: [string, QueryValueType][], childCount: number, ctor: any, deps: ([DepFlags, any] | any)[], props?: {[name: string]: [number, string]}, 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[] = []; if (props) { for (let prop in props) { const [bindingIndex, nonMinifiedName] = props[prop]; bindings[bindingIndex] = { - type: BindingType.ProviderProperty, + type: BindingType.DirectiveProperty, name: prop, nonMinifiedName, securityContext: undefined, suffix: undefined }; } } - const outputDefs: ProviderOutputDef[] = []; + const outputDefs: DirectiveOutputDef[] = []; if (outputs) { for (let propName in outputs) { 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 => { let token: any; let flags: DepFlags; @@ -86,7 +96,7 @@ export function _providerDef( } return { - type: NodeType.Provider, + type, // will bet set by the view definition index: undefined, reverseChildIndex: undefined, @@ -99,14 +109,13 @@ export function _providerDef( flags, matchedQueries: matchedQueryDefs, ngContentIndex: undefined, childCount, bindings, - disposableCount: outputDefs.length, + disposableCount: outputs.length, element: undefined, provider: { - type, + type: providerType, token, tokenKey: tokenKey(token), value, - deps: depDefs, - outputs: outputDefs, component + deps: depDefs, outputs, component }, text: undefined, pureExpression: undefined, @@ -116,97 +125,115 @@ export function _providerDef( } 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; - 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) { 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, 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; // Note: fallthrough is intended! switch (def.bindings.length) { case 10: - changes = checkAndUpdateProp(view, provider, def, 9, v9, changes); + changes = checkAndUpdateProp(view, providerData, def, 9, v9, changes); case 9: - changes = checkAndUpdateProp(view, provider, def, 8, v8, changes); + changes = checkAndUpdateProp(view, providerData, def, 8, v8, changes); case 8: - changes = checkAndUpdateProp(view, provider, def, 7, v7, changes); + changes = checkAndUpdateProp(view, providerData, def, 7, v7, changes); case 7: - changes = checkAndUpdateProp(view, provider, def, 6, v6, changes); + changes = checkAndUpdateProp(view, providerData, def, 6, v6, changes); case 6: - changes = checkAndUpdateProp(view, provider, def, 5, v5, changes); + changes = checkAndUpdateProp(view, providerData, def, 5, v5, changes); case 5: - changes = checkAndUpdateProp(view, provider, def, 4, v4, changes); + changes = checkAndUpdateProp(view, providerData, def, 4, v4, changes); case 4: - changes = checkAndUpdateProp(view, provider, def, 3, v3, changes); + changes = checkAndUpdateProp(view, providerData, def, 3, v3, changes); case 3: - changes = checkAndUpdateProp(view, provider, def, 2, v2, changes); + changes = checkAndUpdateProp(view, providerData, def, 2, v2, changes); case 2: - changes = checkAndUpdateProp(view, provider, def, 1, v1, changes); + changes = checkAndUpdateProp(view, providerData, def, 1, v1, changes); case 1: - changes = checkAndUpdateProp(view, provider, def, 0, v0, changes); + changes = checkAndUpdateProp(view, providerData, def, 0, v0, changes); } if (changes) { - provider.ngOnChanges(changes); + directive.ngOnChanges(changes); } if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { - provider.ngOnInit(); + directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { - provider.ngDoCheck(); + directive.ngDoCheck(); } } -export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, values: any[]) { - const provider = asProviderData(view, def.index).instance; +export function checkAndUpdateDirectiveDynamic(view: ViewData, def: NodeDef, values: any[]) { + const providerData = asProviderData(view, def.index); + const directive = providerData.instance; let changes: SimpleChanges; 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) { - provider.ngOnChanges(changes); + directive.ngOnChanges(changes); } if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { - provider.ngOnInit(); + directive.ngOnInit(); } if (def.flags & NodeFlags.DoCheck) { - provider.ngDoCheck(); + directive.ngDoCheck(); } } -function createInstance(view: ViewData, nodeDef: NodeDef): any { - const providerDef = nodeDef.provider; +function _createProviderInstance(view: ViewData, def: NodeDef): any { + const providerDef = def.provider; let injectable: any; switch (providerDef.type) { case ProviderType.Class: - injectable = - createClass(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps); + injectable = createClass(view, def.index, def.parent, providerDef.value, providerDef.deps); break; case ProviderType.Factory: - injectable = - callFactory(view, nodeDef.index, nodeDef.parent, providerDef.value, providerDef.deps); + injectable = callFactory(view, def.index, def.parent, providerDef.value, providerDef.deps); break; case ProviderType.UseExisting: - injectable = resolveDep(view, nodeDef.index, nodeDef.parent, providerDef.deps[0]); + injectable = resolveDep(view, def.index, def.parent, providerDef.deps[0]); break; case ProviderType.Value: injectable = providerDef.value; 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; } @@ -291,7 +318,7 @@ export function resolveDep( requestNodeIndex = null; elIndex = view.def.nodes[elIndex].parent; while (elIndex == null && view) { - elIndex = viewParentDiIndex(view); + elIndex = viewParentElIndex(view); view = view.parent; } } @@ -334,20 +361,20 @@ export function resolveDep( if (providerIndex != null) { const providerData = asProviderData(view, providerIndex); if (providerData.instance === NOT_CREATED) { - providerData.instance = createInstance(view, view.def.nodes[providerIndex]); + providerData.instance = _createProviderInstance(view, view.def.nodes[providerIndex]); } return providerData.instance; } } requestNodeIndex = null; - elIndex = viewParentDiIndex(view); + elIndex = viewParentElIndex(view); view = view.parent; } return startView.root.injector.get(depDef.token, notFoundValue); } 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 { let change: SimpleChange; let changed: boolean; @@ -361,13 +388,18 @@ function checkAndUpdateProp( changed = checkAndUpdateBinding(view, def, bindingIdx, value); } 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 propName = binding.name; // Note: This is still safe with Closure Compiler as // the user passed in the property name as an object has to `providerDef`, // so Closure Compiler will have renamed the property correctly already. - provider[propName] = value; + providerData.instance[propName] = value; if (change) { changes = changes || {}; changes[binding.nonMinifiedName] = change; @@ -382,7 +414,7 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node } const len = view.def.nodes.length; 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 nodeIndex = nodeDef.index; if (nodeDef.flags & lifecycles) { diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index ef5741d201..dd72c21d83 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -7,24 +7,22 @@ */ 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 { - return _pureExpressionDef( - PureExpressionType.Pipe, new Array(argCount), - {token: pipeToken, tokenKey: tokenKey(pipeToken), flags: DepFlags.None}); +export function purePipeDef(argCount: number): NodeDef { + // argCount + 1 to include the pipe as first arg + return _pureExpressionDef(PureExpressionType.Pipe, new Array(argCount + 1)); } 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 { - return _pureExpressionDef(PureExpressionType.Object, propertyNames, undefined); + return _pureExpressionDef(PureExpressionType.Object, propertyNames); } -function _pureExpressionDef( - type: PureExpressionType, propertyNames: string[], pipeDep: DepDef): NodeDef { +function _pureExpressionDef(type: PureExpressionType, propertyNames: string[]): NodeDef { const bindings: BindingDef[] = new Array(propertyNames.length); for (let i = 0; i < propertyNames.length; i++) { const prop = propertyNames[i]; @@ -55,17 +53,14 @@ function _pureExpressionDef( element: undefined, provider: undefined, text: undefined, - pureExpression: {type, pipeDep}, + pureExpression: {type}, query: undefined, ngContent: undefined }; } export function createPureExpression(view: ViewData, def: NodeDef): PureExpressionData { - const pipe = def.pureExpression.pipeDep ? - Services.resolveDep(view, def.index, def.parent, def.pureExpression.pipeDep) : - undefined; - return {value: undefined, pipe}; + return {value: undefined}; } export function checkAndUpdatePureExpressionInline( @@ -99,17 +94,6 @@ export function checkAndUpdatePureExpressionInline( const data = asPureExpressionData(view, def.index); 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; switch (def.pureExpression.type) { case PureExpressionType.Array: @@ -165,36 +149,37 @@ export function checkAndUpdatePureExpressionInline( } break; case PureExpressionType.Pipe: + const pipe = v0; switch (bindings.length) { 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; 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; 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; case 7: - value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6); + value = pipe.transform(v1, v2, v3, v4, v5, v6); break; case 6: - value = data.pipe.transform(v0, v1, v2, v3, v4, v5); + value = pipe.transform(v1, v2, v3, v4, v5); break; case 5: - value = data.pipe.transform(v0, v1, v2, v3, v4); + value = pipe.transform(v1, v2, v3, v4); break; case 4: - value = data.pipe.transform(v0, v1, v2, v3); + value = pipe.transform(v1, v2, v3); break; case 3: - value = data.pipe.transform(v0, v1, v2); + value = pipe.transform(v1, v2); break; case 2: - value = data.pipe.transform(v0, v1); + value = pipe.transform(v1); break; case 1: - value = data.pipe.transform(v0); + value = pipe.transform(v0); break; } break; @@ -219,23 +204,18 @@ export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef let value: any; switch (def.pureExpression.type) { case PureExpressionType.Array: - value = new Array(values.length); - for (let i = 0; i < values.length; i++) { - value[i] = unwrapValue(values[i]); - } + value = values; break; case PureExpressionType.Object: value = {}; for (let i = 0; i < values.length; i++) { - value[bindings[i].name] = unwrapValue(values[i]); + value[bindings[i].name] = values[i]; } break; case PureExpressionType.Pipe: - const params = new Array(values.length); - for (let i = 0; i < values.length; i++) { - params[i] = unwrapValue(values[i]); - } - value = (data.pipe.transform)(...params); + const pipe = values[0]; + const params = values.slice(1); + value = (pipe.transform)(...params); break; } data.value = value; diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index 4b8f754e3b..fbd2995953 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -13,7 +13,7 @@ import {ViewContainerRef} from '../linker/view_container_ref'; import {createTemplateRef, createViewContainerRef} from './refs'; 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( flags: NodeFlags, id: string, bindings: {[propName: string]: QueryBindingType}): NodeDef { @@ -54,16 +54,18 @@ export function createQuery(): QueryList { } export function dirtyParentQuery(queryId: string, view: ViewData) { - let nodeIndex = view.parentIndex; + let elIndex = viewParentElIndex(view); view = view.parent; let queryIdx: number; while (view) { - const elementDef = view.def.nodes[nodeIndex]; - queryIdx = elementDef.element.providerIndices[queryId]; - if (queryIdx != null) { - break; + if (elIndex != null) { + const elementDef = view.def.nodes[elIndex]; + queryIdx = elementDef.element.providerIndices[queryId]; + if (queryIdx != null) { + break; + } } - nodeIndex = view.parentIndex; + elIndex = viewParentElIndex(view); view = view.parent; } if (!view) { diff --git a/modules/@angular/core/src/view/refs.ts b/modules/@angular/core/src/view/refs.ts index 159f19a1f8..9fa8afd54a 100644 --- a/modules/@angular/core/src/view/refs.ts +++ b/modules/@angular/core/src/view/refs.ts @@ -19,8 +19,8 @@ import {Sanitizer, SecurityContext} from '../security'; import {Type} from '../type'; 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 {isComponentView, renderNode, resolveViewDefinition, rootRenderNodes, tokenKey, viewParentDiIndex} from './util'; +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, viewParentElIndex} from './util'; const EMPTY_CONTEXT = new Object(); @@ -53,7 +53,7 @@ class ComponentFactory_ implements ComponentFactory { const len = viewDef.nodes.length; for (let i = 0; i < len; i++) { const nodeDef = viewDef.nodes[i]; - if (nodeDef.provider && nodeDef.provider.component) { + if (nodeDef.flags & NodeFlags.HasComponent) { componentNodeIndex = i; break; } @@ -99,7 +99,7 @@ class ViewContainerRef_ implements ViewContainerRef { let view = this._view; let elIndex = view.def.nodes[this._elIndex].parent; while (elIndex == null && view) { - elIndex = viewParentDiIndex(view); + elIndex = viewParentElIndex(view); view = view.parent; } return view ? new Injector_(view, elIndex) : this._view.root.injector; diff --git a/modules/@angular/core/src/view/services.ts b/modules/@angular/core/src/view/services.ts index ca98c79dd1..903b234bed 100644 --- a/modules/@angular/core/src/view/services.ts +++ b/modules/@angular/core/src/view/services.ts @@ -19,8 +19,8 @@ import {resolveDep} from './provider'; import {getQueryValue} from './query'; import {createInjector} from './refs'; 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 {checkBinding, isComponentView, queryIdIsReference, renderNode, resolveViewDefinition, rootRenderNodes, viewParentDiIndex} from './util'; +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, viewParentElIndex} from './util'; import {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView} from './view'; import {attachEmbeddedView, detachEmbeddedView, moveEmbeddedView} from './view_attach'; @@ -44,7 +44,8 @@ export function initServicesIfNeeded() { Services.resolveDep = services.resolveDep; Services.createDebugContext = services.createDebugContext; Services.handleEvent = services.handleEvent; - Services.updateView = services.updateView; + Services.updateDirectives = services.updateDirectives; + Services.updateRenderer = services.updateRenderer; } function createProdServices() { @@ -62,7 +63,9 @@ function createProdServices() { createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), handleEvent: (view: ViewData, nodeIndex: number, eventName: string, event: any) => 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, createDebugContext: (view: ViewData, nodeIndex: number) => new DebugContext_(view, nodeIndex), handleEvent: debugHandleEvent, - updateView: debugUpdateView + updateDirectives: debugUpdateDirectives, + updateRenderer: debugUpdateRenderer }; } @@ -152,37 +156,56 @@ function debugHandleEvent(view: ViewData, nodeIndex: number, eventName: string, '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) { throw viewDestroyedError(_currentAction); } - debugSetCurrentNode(view, nextNodeIndexWithBinding(view, 0)); - return view.def.update(debugCheckFn, view); + debugSetCurrentNode(view, nextDirectiveWithBinding(view, 0)); + return view.def.updateDirectives(debugCheckDirectivesFn, view); - function debugCheckFn( - view: ViewData, nodeIndex: number, argStyle: ArgumentType, v0?: any, v1?: any, v2?: any, - v3?: any, v4?: any, v5?: any, v6?: any, v7?: any, v8?: any, v9?: any) { - const values = argStyle === ArgumentType.Dynamic ? v0 : [].slice.call(arguments, 3); - const nodeDef = view.def.nodes[nodeIndex]; - for (let i = 0; i < nodeDef.bindings.length; i++) { - const binding = nodeDef.bindings[i]; - const value = values[i]; - if ((binding.type === BindingType.ElementProperty || - binding.type === BindingType.ProviderProperty) && - checkBinding(view, nodeDef, i, value)) { - const elIndex = nodeDef.type === NodeType.Provider ? nodeDef.parent : nodeDef.index; - setBindingDebugInfo( - view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, - value); - } - } - const result = check(view, nodeIndex, argStyle, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - - debugSetCurrentNode(view, nextNodeIndexWithBinding(view, nodeIndex)); + 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( + delegate: NodeCheckFn, view: ViewData, nodeIndex: number, argStyle: ArgumentType, + givenValues: any[]) { + const values = argStyle === ArgumentType.Dynamic ? givenValues[0] : givenValues; + const nodeDef = view.def.nodes[nodeIndex]; + for (let i = 0; i < nodeDef.bindings.length; i++) { + const binding = nodeDef.bindings[i]; + const value = values[i]; + if ((binding.type === BindingType.ElementProperty || + binding.type === BindingType.DirectiveProperty) && + checkBinding(view, nodeDef, i, value)) { + const elIndex = nodeDef.type === NodeType.Directive ? nodeDef.parent : nodeDef.index; + setBindingDebugInfo( + view.root.renderer, asElementData(view, elIndex).renderElement, binding.nonMinifiedName, + value); + } + } + return (delegate)(view, nodeIndex, argStyle, ...givenValues); +}; + function setBindingDebugInfo(renderer: RendererV2, renderNode: any, propName: string, value: any) { const renderName = `ng-reflect-${camelCaseToDashCase(propName)}`; if (value) { @@ -203,16 +226,26 @@ function camelCaseToDashCase(input: string): string { 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++) { 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 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 { constructor(private _delegate: RendererV2) {} @@ -282,7 +315,7 @@ class DebugContext_ implements DebugContext { } if (elIndex == null) { while (elIndex == null && elView) { - elIndex = viewParentDiIndex(elView); + elIndex = viewParentElIndex(elView); elView = elView.parent; } } @@ -320,7 +353,7 @@ class DebugContext_ implements DebugContext { if (this.elDef) { for (let i = this.elDef.index + 1; i <= this.elDef.index + this.elDef.childCount; 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); } 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++) { 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); } i += childDef.childCount; @@ -368,7 +401,7 @@ function findHostElement(view: ViewData): ElementData { view = view.parent; } if (view.parent) { - const hostData = asElementData(view.parent, view.parentIndex); + const hostData = asElementData(view.parent, viewParentElIndex(view)); return hostData; } return undefined; @@ -397,6 +430,7 @@ function callWithDebugContext(action: string, fn: any, self: any, args: any[]) { if (isViewDebugError(e) || !_currentView) { throw e; } + _currentView.state |= ViewState.Errored; throw viewWrappedDebugError(e, getCurrentDebugContext()); } } diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index 5697f411c0..356e45ef11 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -10,7 +10,7 @@ import {isDevMode} from '../application_ref'; import {looseIdentical} from '../facade/lang'; 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 { // 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); for (let i = 1; i < constants.length; i++) { bindings[i - 1] = { - type: BindingType.Interpolation, + type: BindingType.TextInterpolation, name: undefined, nonMinifiedName: undefined, securityContext: undefined, @@ -143,7 +143,6 @@ export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: } function _addInterpolationPart(value: any, binding: BindingDef): string { - value = unwrapValue(value); const valueStr = value != null ? value.toString() : ''; return valueStr + binding.suffix; } diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 5755257024..1a15a1d78e 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -23,7 +23,8 @@ import {Sanitizer, SecurityContext} from '../security'; export interface ViewDefinition { flags: ViewFlags; component: ComponentDefinition; - update: ViewUpdateFn; + updateDirectives: ViewUpdateFn; + updateRenderer: ViewUpdateFn; handleEvent: ViewHandleEventFn; /** * Order: Depth first. @@ -124,7 +125,9 @@ export interface NodeDef { export enum NodeType { Element, Text, + Directive, Provider, + Pipe, PureExpression, Query, NgContent @@ -163,8 +166,8 @@ export enum BindingType { ElementClass, ElementStyle, ElementProperty, - ProviderProperty, - Interpolation, + DirectiveProperty, + TextInterpolation, PureExpressionProperty } @@ -200,7 +203,7 @@ export interface ProviderDef { tokenKey: string; value: any; deps: DepDef[]; - outputs: ProviderOutputDef[]; + outputs: DirectiveOutputDef[]; // closure to allow recursive components component: ViewDefinitionFactory; } @@ -228,7 +231,7 @@ export enum DepFlags { Value = 2 << 2 } -export interface ProviderOutputDef { +export interface DirectiveOutputDef { propName: string; eventName: string; } @@ -238,10 +241,7 @@ export interface TextDef { source: string; } -export interface PureExpressionDef { - type: PureExpressionType; - pipeDep: DepDef; -} +export interface PureExpressionDef { type: PureExpressionType; } export enum PureExpressionType { Array, @@ -285,8 +285,7 @@ export interface NgContentDef { export interface ViewData { def: ViewDefinition; root: RootData; - // index of parent element / anchor. Not the index - // of the provider with the component view. + // index of component provider / anchor. parentIndex: number; parent: ViewData; component: any; @@ -385,10 +384,7 @@ export function asProviderData(view: ViewData, index: number): ProviderData { * * Attention: Adding fields to this is performance sensitive! */ -export interface PureExpressionData { - value: any; - pipe: PipeTransform; -} +export interface PureExpressionData { value: any; } /** * Accessor for view.nodes, enforcing that every usage site stays monomorphic. @@ -493,7 +489,8 @@ export interface Services { notFoundValue?: any): any; createDebugContext(view: ViewData, nodeIndex: number): DebugContext; handleEvent: ViewHandleEventFn; - updateView: ViewUpdateFn; + updateDirectives: ViewUpdateFn; + updateRenderer: ViewUpdateFn; } /** @@ -513,5 +510,6 @@ export const Services: Services = { resolveDep: undefined, createDebugContext: undefined, handleEvent: undefined, - updateView: undefined, + updateDirectives: undefined, + updateRenderer: undefined, }; diff --git a/modules/@angular/core/src/view/util.ts b/modules/@angular/core/src/view/util.ts index 0af4fdb2de..16ff45725e 100644 --- a/modules/@angular/core/src/view/util.ts +++ b/modules/@angular/core/src/view/util.ts @@ -30,16 +30,28 @@ export function tokenKey(token: any): string { return key; } +let unwrapCounter = 0; + +export function unwrapValue(value: any): any { + if (value instanceof WrappedValue) { + value = value.wrapped; + unwrapCounter++; + } + return value; +} + export function checkBinding( view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { 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( view: ViewData, def: NodeDef, bindingIdx: number, value: any) { 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( Services.createDebugContext(view, def.index), oldValue, value, (view.state & ViewState.FirstCheck) !== 0); @@ -49,15 +61,10 @@ export function checkBindingNoChanges( export function checkAndUpdateBinding( view: ViewData, def: NodeDef, bindingIdx: number, value: any): boolean { const oldValues = view.oldValues; - if ((view.state & ViewState.FirstCheck) || + if (unwrapCounter || (view.state & ViewState.FirstCheck) || !looseIdentical(oldValues[def.bindingIndex + bindingIdx], value)) { + unwrapCounter = 0; 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 false; @@ -75,13 +82,6 @@ export function dispatchEvent( 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 { if (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 * that contains the view container. */ -export function viewParentDiIndex(view: ViewData): number { - if (view.parent && view.context !== view.component) { - const parentNodeDef = view.parent.def.nodes[view.parentIndex]; - return parentNodeDef.parent; +export function viewParentElIndex(view: ViewData): number { + const parentView = view.parent; + if (parentView) { + return parentView.def.nodes[view.parentIndex].parent; + } else { + return null; } - return view.parentIndex; } 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; case NodeType.Text: return asTextData(view, def.index).renderText; + case NodeType.Directive: + case NodeType.Pipe: case NodeType.Provider: return asProviderData(view, def.index).instance; } @@ -183,7 +186,10 @@ export function visitRootRenderNodes( const len = view.def.nodes.length; for (let i = 0; i < len; i++) { const nodeDef = view.def.nodes[i]; - visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target); + if (nodeDef.type === NodeType.Element || nodeDef.type === NodeType.Text || + nodeDef.type === NodeType.NgContent) { + visitRenderNode(view, nodeDef, action, parentNode, nextSibling, target); + } // jump to next sibling i += nodeDef.childCount; } @@ -197,7 +203,7 @@ export function visitProjectedRenderNodes( compView = 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 endIndex = hostElDef.index + hostElDef.childCount; for (let i = startIndex; i <= endIndex; i++) { diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 38133e8af2..3a7e8c6deb 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -11,19 +11,19 @@ import {ViewEncapsulation} from '../metadata/view'; import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element'; import {expressionChangedAfterItHasBeenCheckedError} from './errors'; 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 {checkAndUpdateQuery, createQuery, queryDef} from './query'; 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 {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition} from './util'; +import {checkBindingNoChanges, isComponentView, queryIdIsReference, resolveViewDefinition, viewParentElIndex} from './util'; const NOOP = (): any => undefined; export function viewDef( - flags: ViewFlags, nodesWithoutIndices: NodeDef[], update?: ViewUpdateFn, - handleEvent?: ViewHandleEventFn, compId?: string, encapsulation?: ViewEncapsulation, - styles?: string[]): ViewDefinition { + flags: ViewFlags, nodesWithoutIndices: NodeDef[], updateDirectives?: ViewUpdateFn, + updateRenderer?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, compId?: string, + encapsulation?: ViewEncapsulation, styles?: string[]): ViewDefinition { // clone nodes and set auto calculated values if (nodesWithoutIndices.length === 0) { throw new Error(`Illegal State: Views without nodes are not allowed!`); @@ -83,7 +83,7 @@ export function viewDef( if (!currentParent) { lastRootNode = node; } - if (node.provider) { + if (node.type === NodeType.Provider || node.type === NodeType.Directive) { currentParent.element.providerIndices[node.provider.tokenKey] = i; } if (node.query) { @@ -108,7 +108,8 @@ export function viewDef( nodeFlags: viewNodeFlags, nodeMatchedQueries: viewMatchedQueries, flags, nodes: nodes, reverseChildNodes, - update: update || NOOP, + updateDirectives: updateDirectives || NOOP, + updateRenderer: updateRenderer || NOOP, handleEvent: handleEvent || NOOP, component: componentDef, 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}!`); } } - if (node.provider) { + if (node.type === NodeType.Provider || node.type === NodeType.Directive) { const parentType = parent ? parent.type : null; if (parentType !== NodeType.Element) { 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) { const parentType = parent ? parent.type : null; - if (parentType !== NodeType.Provider) { + if (parentType !== NodeType.Directive) { 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) { @@ -282,7 +283,7 @@ function initView(view: ViewData, component: any, context: any) { function createViewNodes(view: ViewData) { let renderHost: any; if (isComponentView(view)) { - renderHost = asElementData(view.parent, view.parentIndex).renderElement; + renderHost = asElementData(view.parent, viewParentElIndex(view)).renderElement; } const def = view.def; @@ -297,23 +298,36 @@ function createViewNodes(view: ViewData) { case NodeType.Text: nodes[i] = createText(view, renderHost, nodeDef) as any; break; - case NodeType.Provider: - if (nodeDef.provider.component) { + case NodeType.Provider: { + const instance = createProviderInstance(view, nodeDef); + const providerData = {componentView: undefined, instance}; + nodes[i] = providerData as any; + break; + } + case NodeType.Pipe: { + const instance = createPipeInstance(view, nodeDef); + const 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 // the component view. Therefore, we create the component view first // and set the ProviderData in ViewData, and then instantiate the provider. const componentView = createView( - view.root, view, nodeDef.parent, resolveViewDefinition(nodeDef.provider.component)); + view.root, view, nodeDef.index, resolveViewDefinition(nodeDef.provider.component)); const providerData = {componentView, instance: undefined}; nodes[i] = providerData as any; - const instance = providerData.instance = createProviderInstance(view, nodeDef); + const instance = providerData.instance = createDirectiveInstance(view, nodeDef); initView(componentView, instance, instance); } else { - const instance = createProviderInstance(view, nodeDef); + const instance = createDirectiveInstance(view, nodeDef); const providerData = {componentView: undefined, instance}; nodes[i] = providerData as any; } break; + } case NodeType.PureExpression: nodes[i] = createPureExpression(view, nodeDef) as any; break; @@ -333,21 +347,25 @@ function createViewNodes(view: ViewData) { } export function checkNoChangesView(view: ViewData) { - Services.updateView(checkNoChangesNode, view); + Services.updateDirectives(checkNoChangesNode, view); execEmbeddedViewsAction(view, ViewAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckNoChanges); + Services.updateRenderer(checkNoChangesNode, view); execComponentViewsAction(view, ViewAction.CheckNoChanges); execQueriesAction(view, NodeFlags.HasViewQuery, QueryAction.CheckNoChanges); } export function checkAndUpdateView(view: ViewData) { - Services.updateView(checkAndUpdateNode, view); + Services.updateDirectives(checkAndUpdateNode, view); execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate); execQueriesAction(view, NodeFlags.HasContentQuery, QueryAction.CheckAndUpdate); callLifecycleHooksChildrenFirst( view, NodeFlags.AfterContentChecked | (view.state & ViewState.FirstCheck ? NodeFlags.AfterContentInit : 0)); + + Services.updateRenderer(checkAndUpdateNode, view); + execComponentViewsAction(view, ViewAction.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); case NodeType.Text: return checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); - case NodeType.Provider: - return checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); + case NodeType.Directive: + return checkAndUpdateDirectiveInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9); case NodeType.PureExpression: return checkAndUpdatePureExpressionInline( 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); case NodeType.Text: return checkAndUpdateTextDynamic(view, nodeDef, values); - case NodeType.Provider: - return checkAndUpdateProviderDynamic(view, nodeDef, values); + case NodeType.Directive: + return checkAndUpdateDirectiveDynamic(view, nodeDef, values); case NodeType.PureExpression: return checkAndUpdatePureExpressionDynamic(view, nodeDef, values); } @@ -462,14 +480,14 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) { } export function destroyView(view: ViewData) { + execEmbeddedViewsAction(view, ViewAction.Destroy); + execComponentViewsAction(view, ViewAction.Destroy); callLifecycleHooksChildrenFirst(view, NodeFlags.OnDestroy); if (view.disposables) { for (let i = 0; i < view.disposables.length; i++) { view.disposables[i](); } } - execComponentViewsAction(view, ViewAction.Destroy); - execEmbeddedViewsAction(view, ViewAction.Destroy); view.state |= ViewState.Destroyed; } diff --git a/modules/@angular/core/test/linker/change_detection_integration_spec.ts b/modules/@angular/core/test/linker/change_detection_integration_spec.ts index c54a1af499..6058f560b9 100644 --- a/modules/@angular/core/test/linker/change_detection_integration_spec.ts +++ b/modules/@angular/core/test/linker/change_detection_integration_spec.ts @@ -6,6 +6,7 @@ * 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 {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'; @@ -19,6 +20,19 @@ import {MockSchemaRegistry} from '../../../compiler/testing/index'; import {EventEmitter} from '../../src/facade/async'; 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 renderLog: RenderLog; let directiveLog: DirectiveLog; @@ -1080,7 +1094,8 @@ export function main() { 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' ]); })); @@ -1092,8 +1107,9 @@ export function main() { it('should throw when a record gets changed after it has been checked', fakeAsync(() => { const ctx = createCompFixture('
', TestData); ctx.componentInstance.a = 1; + 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(() => { @@ -1216,26 +1232,28 @@ export function main() { expect(renderLog.loggedValues).toEqual(['Tom']); }); - it('should recurse into nested view containers even if there are no bindings in the component view', - () => { - @Component({template: ''}) - class Comp { - name = 'Tom'; - @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; - @ViewChild(TemplateRef) template: TemplateRef; - } + // 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', + () => { + @Component({template: ''}) + class Comp { + name = 'Tom'; + @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; + @ViewChild(TemplateRef) template: TemplateRef; + } - TestBed.configureTestingModule({declarations: [Comp]}); - initHelpers(); + TestBed.configureTestingModule({declarations: [Comp]}); + initHelpers(); - const ctx = TestBed.createComponent(Comp); - ctx.detectChanges(); - expect(renderLog.loggedValues).toEqual([]); + const ctx = TestBed.createComponent(Comp); + ctx.detectChanges(); + expect(renderLog.loggedValues).toEqual([]); - ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template); - ctx.detectChanges(); - expect(renderLog.loggedValues).toEqual(['Tom']); - }); + ctx.componentInstance.vc.createEmbeddedView(ctx.componentInstance.template); + ctx.detectChanges(); + expect(renderLog.loggedValues).toEqual(['Tom']); + }); }); }); } diff --git a/modules/@angular/core/test/linker/integration_spec.ts b/modules/@angular/core/test/linker/integration_spec.ts index 78a9cf1b9c..1963e4154a 100644 --- a/modules/@angular/core/test/linker/integration_spec.ts +++ b/modules/@angular/core/test/linker/integration_spec.ts @@ -265,7 +265,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea }); describe('pipes', () => { - viewEngine || it('should support pipes in bindings', () => { + it('should support pipes in bindings', () => { TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]}); const template = '
'; TestBed.overrideComponent(MyComp, {set: {template}}); @@ -545,7 +545,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea }); 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]}); const template = ''; @@ -670,31 +670,29 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea }); if (getDOM().supportsDOMEvents()) { - viewEngine || - it('should be checked when an async pipe requests a check', fakeAsync(() => { - TestBed.configureTestingModule( - {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); - const template = ''; - TestBed.overrideComponent(MyComp, {set: {template}}); - const fixture = TestBed.createComponent(MyComp); + it('should be checked when an async pipe requests a check', fakeAsync(() => { + TestBed.configureTestingModule( + {declarations: [MyComp, PushCmpWithAsyncPipe], imports: [CommonModule]}); + const template = ''; + TestBed.overrideComponent(MyComp, {set: {template}}); + const fixture = TestBed.createComponent(MyComp); - tick(); + tick(); - const cmp: PushCmpWithAsyncPipe = - fixture.debugElement.children[0].references['cmp']; - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + const cmp: PushCmpWithAsyncPipe = fixture.debugElement.children[0].references['cmp']; + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - fixture.detectChanges(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(1); + fixture.detectChanges(); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); - cmp.resolve(2); - tick(); + cmp.resolve(2); + tick(); - fixture.detectChanges(); - expect(cmp.numberOfChecks).toEqual(2); - })); + fixture.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + })); } }); @@ -897,26 +895,24 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea expect(tc.nativeElement.id).toEqual('newId'); }); - viewEngine || - it('should not use template variables for expressions in hostProperties', () => { - @Directive( - {selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) - class DirectiveWithHostProps { - id = 'one'; - } + it('should not use template variables for expressions in hostProperties', () => { + @Directive({selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) + class DirectiveWithHostProps { + id = 'one'; + } - const fixture = - TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) - .overrideComponent(MyComp, { - set: {template: `
`} - }) - .createComponent(MyComp); - fixture.detectChanges(); + const fixture = + TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithHostProps]}) + .overrideComponent( + MyComp, + {set: {template: `
`}}) + .createComponent(MyComp); + fixture.detectChanges(); - const tc = fixture.debugElement.children[0]; - expect(tc.properties['id']).toBe('one'); - expect(tc.properties['title']).toBe(undefined); - }); + const tc = fixture.debugElement.children[0]; + expect(tc.properties['id']).toBe('one'); + expect(tc.properties['title']).toBe(undefined); + }); it('should not allow pipes in hostProperties', () => { @Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}}) @@ -930,8 +926,7 @@ function declareTests({useJit, viewEngine}: {useJit: boolean, viewEngine: boolea .toThrowError(/Host binding expression cannot contain pipes/); }); - // TODO: literal array - viewEngine || it('should not use template variables for expressions in hostListeners', () => { + it('should not use template variables for expressions in hostListeners', () => { @Directive({selector: '[host-listener]', host: {'(click)': 'doIt(id, unknownProp)'}}) class DirectiveWithHostListener { id = 'one'; diff --git a/modules/@angular/core/test/view/anchor_spec.ts b/modules/@angular/core/test/view/anchor_spec.ts index c6f82d33df..50c1ca1da9 100644 --- a/modules/@angular/core/test/view/anchor_spec.ts +++ b/modules/@angular/core/test/view/anchor_spec.ts @@ -16,8 +16,9 @@ import {createRootView, isBrowser} from './helper'; export function main() { describe(`View Anchor`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition { - return viewDef(ViewFlags.None, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn): ViewDefinition { + return viewDef(ViewFlags.None, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes( diff --git a/modules/@angular/core/test/view/component_view_spec.ts b/modules/@angular/core/test/view/component_view_spec.ts index f91db5993c..3730d63cce 100644 --- a/modules/@angular/core/test/view/component_view_spec.ts +++ b/modules/@angular/core/test/view/component_view_spec.ts @@ -16,9 +16,9 @@ import {createRootView, isBrowser, removeNodes} from './helper'; export function main() { describe(`Component Views`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -119,7 +119,7 @@ export function main() { directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef( [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), - ], update + ], null, update )), ])); const compView = asProviderData(view, 1).componentView; @@ -194,7 +194,7 @@ export function main() { [ 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); })); @@ -246,7 +246,7 @@ export function main() { [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], - update)), + null, update)), ])); const compView = asProviderData(view, 1).componentView; diff --git a/modules/@angular/core/test/view/element_spec.ts b/modules/@angular/core/test/view/element_spec.ts index 5ef0b3b3de..d13a348c07 100644 --- a/modules/@angular/core/test/view/element_spec.ts +++ b/modules/@angular/core/test/view/element_spec.ts @@ -17,9 +17,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, re export function main() { describe(`View Elements`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes( @@ -85,7 +85,7 @@ export function main() { [BindingType.ElementProperty, 'value', SecurityContext.NONE] ]), ], - (check, view) => { + null, (check, view) => { checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); })); @@ -112,7 +112,7 @@ export function main() { [BindingType.ElementAttribute, 'a2', SecurityContext.NONE] ]), ], - (check, view) => { + null, (check, view) => { checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); })); @@ -159,7 +159,7 @@ export function main() { [BindingType.ElementStyle, 'color', null] ]), ], - (check, view) => { + null, (check, view) => { 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()) { describe('listen to DOM events', () => { function createAndAttachAndGetRootNodes(viewDef: ViewDefinition): @@ -228,7 +192,7 @@ export function main() { spyOn(HTMLElement.prototype, 'removeEventListener').and.callThrough(); const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null, - handleEventSpy)); + null, handleEventSpy)); rootNodes[0].click(); @@ -252,7 +216,7 @@ export function main() { [elementDef( NodeFlags.None, null, null, 0, 'button', null, null, [['window', 'windowClick']])], - null, handleEventSpy)); + null, null, handleEventSpy)); expect(addListenerSpy).toHaveBeenCalled(); expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick'); @@ -278,7 +242,7 @@ export function main() { [elementDef( NodeFlags.None, null, null, 0, 'button', null, null, [['document', 'documentClick']])], - null, handleEventSpy)); + null, null, handleEventSpy)); expect(addListenerSpy).toHaveBeenCalled(); expect(addListenerSpy.calls.mostRecent().args[0]).toBe('documentClick'); @@ -302,7 +266,7 @@ export function main() { const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( [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(); return eventHandlerResult; })); @@ -328,7 +292,7 @@ export function main() { const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough(); const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef( [elementDef(NodeFlags.None, null, null, 0, 'button', null, null, ['click'])], null, - () => { throw new Error('Test'); })); + null, () => { throw new Error('Test'); })); let err: any; try { diff --git a/modules/@angular/core/test/view/embedded_view_spec.ts b/modules/@angular/core/test/view/embedded_view_spec.ts index f332ac56b3..5ced178e13 100644 --- a/modules/@angular/core/test/view/embedded_view_spec.ts +++ b/modules/@angular/core/test/view/embedded_view_spec.ts @@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper'; export function main() { describe(`Embedded Views`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { diff --git a/modules/@angular/core/test/view/ng_content_spec.ts b/modules/@angular/core/test/view/ng_content_spec.ts index 8fcd3c0d5a..460117938f 100644 --- a/modules/@angular/core/test/view/ng_content_spec.ts +++ b/modules/@angular/core/test/view/ng_content_spec.ts @@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper'; export function main() { describe(`View NgContent`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { diff --git a/modules/@angular/core/test/view/provider_spec.ts b/modules/@angular/core/test/view/provider_spec.ts index ef0963e839..d1ce2175fc 100644 --- a/modules/@angular/core/test/view/provider_spec.ts +++ b/modules/@angular/core/test/view/provider_spec.ts @@ -17,10 +17,11 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr export function main() { describe(`View Providers`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { return viewDef( - viewFlags, nodes, update, handleEvent, 'someCompId', ViewEncapsulation.None, []); + viewFlags, nodes, updateDirectives, updateRenderer, handleEvent, 'someCompId', + ViewEncapsulation.None, []); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { @@ -59,7 +60,8 @@ export function main() { createAndGetRootNodes(compViewDef([ 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]) ])); @@ -330,37 +332,6 @@ export function main() { 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( NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'}) ], - null, handleEvent)); + null, null, handleEvent)); emitter.emit('someEventInstance'); expect(handleEvent).toHaveBeenCalledWith(view, 0, 'someEventName', 'someEventInstance'); @@ -410,7 +381,7 @@ export function main() { directiveDef( NodeFlags.None, null, 0, SomeService, [], null, {emitter: 'someEventName'}) ], - null, () => { throw new Error('Test'); })); + null, null, () => { throw new Error('Test'); })); let err: any; try { diff --git a/modules/@angular/core/test/view/pure_expression_spec.ts b/modules/@angular/core/test/view/pure_expression_spec.ts index b0078d285a..dd2121a3b0 100644 --- a/modules/@angular/core/test/view/pure_expression_spec.ts +++ b/modules/@angular/core/test/view/pure_expression_spec.ts @@ -7,7 +7,7 @@ */ 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 {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helper'; @@ -15,9 +15,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView} from './helpe export function main() { describe(`View Pure Expressions`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} { @@ -64,32 +64,6 @@ export function main() { 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}); }); - 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( [ 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']}) ], (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]); })); const service = asProviderData(view, 3).instance; @@ -194,36 +143,6 @@ export function main() { 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'); - }); }); }); }); diff --git a/modules/@angular/core/test/view/query_spec.ts b/modules/@angular/core/test/view/query_spec.ts index 2d15622aae..599e39e56c 100644 --- a/modules/@angular/core/test/view/query_spec.ts +++ b/modules/@angular/core/test/view/query_spec.ts @@ -17,9 +17,9 @@ import {createRootView} from './helper'; export function main() { describe(`Query Views`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function embeddedViewDef(nodes: NodeDef[], update?: ViewUpdateFn): ViewDefinitionFactory { diff --git a/modules/@angular/core/test/view/services_spec.ts b/modules/@angular/core/test/view/services_spec.ts index b0cb010462..f705c73821 100644 --- a/modules/@angular/core/test/view/services_spec.ts +++ b/modules/@angular/core/test/view/services_spec.ts @@ -16,9 +16,9 @@ import {createRootView, isBrowser} from './helper'; export function main() { describe('View Services', () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes( diff --git a/modules/@angular/core/test/view/text_spec.ts b/modules/@angular/core/test/view/text_spec.ts index f68a7ad875..aad9b1e4a2 100644 --- a/modules/@angular/core/test/view/text_spec.ts +++ b/modules/@angular/core/test/view/text_spec.ts @@ -16,9 +16,9 @@ import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser} fr export function main() { describe(`View Text`, () => { function compViewDef( - nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn, - viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { - return viewDef(viewFlags, nodes, update, handleEvent); + nodes: NodeDef[], updateDirectives?: ViewUpdateFn, updateRenderer?: ViewUpdateFn, + handleEvent?: ViewHandleEventFn, viewFlags: ViewFlags = ViewFlags.None): ViewDefinition { + return viewDef(viewFlags, nodes, updateDirectives, updateRenderer, handleEvent); } function createAndGetRootNodes( @@ -67,7 +67,7 @@ export function main() { [ textDef(null, ['0', '1', '2']), ], - (check, view) => { + null, (check, view) => { checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['a', 'b']); })); @@ -77,41 +77,6 @@ export function main() { 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'); - }); - } }); }); diff --git a/modules/benchmarks/src/tree/ng2_next/tree.ts b/modules/benchmarks/src/tree/ng2_next/tree.ts index 9bbee91c9e..cde503adb3 100644 --- a/modules/benchmarks/src/tree/ng2_next/tree.ts +++ b/modules/benchmarks/src/tree/ng2_next/tree.ts @@ -75,10 +75,13 @@ function TreeComponent_0(): ViewDefinition { ], (check, view) => { 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, 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); }); }