From bd149e5d674fd15971f80d81d2871b4a94d48c48 Mon Sep 17 00:00:00 2001 From: JoostK Date: Tue, 15 May 2018 21:07:59 +0200 Subject: [PATCH] fix(ivy): compile interpolated bindings without superfluous bind instruction (#23923) This fixes the case where the compiler would generate a bind(interpolation#()) instruction. PR Close #23923 --- .../compiler/src/render3/r3_identifiers.ts | 2 +- .../compiler/src/render3/view/template.ts | 27 ++-- .../render3/r3_view_compiler_binding_spec.ts | 123 ++++++++++++++++++ 3 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 packages/compiler/test/render3/r3_view_compiler_binding_spec.ts diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index bbde7f2f02..80db996ec2 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -33,7 +33,7 @@ export class Identifiers { static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE}; - static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; + static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE}; static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index e79e802377..91e2f4b8b9 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; -import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; +import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast'; import {Lexer} from '../../expression_parser/lexer'; import {Parser} from '../../expression_parser/parser'; import * as html from '../../ml_parser/ast'; @@ -340,10 +340,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const instruction = BINDING_INSTRUCTION_MAP[input.type]; if (instruction) { // TODO(chuckj): runtime: security context? - const value = o.importExpr(R3.bind).callFn([convertedBinding]); this.instruction( this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex), - o.literal(input.name), value); + o.literal(input.name), convertedBinding); } else { this._unsupported(`binding type ${input.type}`); } @@ -418,7 +417,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver const convertedBinding = this.convertPropertyBinding(context, input.value); this.instruction( this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex), - o.literal(input.name), o.importExpr(R3.bind).callFn([convertedBinding])); + o.literal(input.name), convertedBinding); }); // Create the template function @@ -443,7 +442,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex)); this.instruction( - this._bindingCode, text.sourceSpan, R3.textCreateBound, o.literal(nodeIndex), + this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex), this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value)); } @@ -483,11 +482,19 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression { const pipesConvertedValue = value.visit(this._valueConverter); - const convertedPropertyBinding = convertPropertyBinding( - this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, - interpolate); - this._bindingCode.push(...convertedPropertyBinding.stmts); - return convertedPropertyBinding.currValExpr; + if (pipesConvertedValue instanceof Interpolation) { + const convertedPropertyBinding = convertPropertyBinding( + this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, + interpolate); + this._bindingCode.push(...convertedPropertyBinding.stmts); + return convertedPropertyBinding.currValExpr; + } else { + const convertedPropertyBinding = convertPropertyBinding( + this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple, + () => error('Unexpected interpolation')); + this._bindingCode.push(...convertedPropertyBinding.stmts); + return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]); + } } } diff --git a/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts new file mode 100644 index 0000000000..8b0a9edcbe --- /dev/null +++ b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts @@ -0,0 +1,123 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {MockDirectory, setup} from '../aot/test_util'; +import {compile, expectEmit} from './mock_compile'; + +describe('compiler compliance: bindings', () => { + const angularFiles = setup({ + compileAngular: true, + compileAnimations: false, + compileCommon: false, + }); + + describe('text bindings', () => { + it('should generate interpolation instruction', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + @Component({ + selector: 'my-component', + template: \` +
Hello {{ name }}
\` + }) + export class MyComponent { + name = 'World'; + } + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'div'); + $i0$.ɵT(1); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵt(1, $i0$.ɵi1('Hello ', $ctx$.name, '')); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect interpolated text binding'); + }); + }); + + describe('property bindings', () => { + it('should generate bind instruction', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-app', + template: '' + }) + export class MyComponent { + title = 'Hello World'; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {}` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'a'); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title)); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect property binding'); + }); + + it('should generate interpolation instruction for {{...}} bindings', () => { + const files: MockDirectory = { + app: { + 'example.ts': ` + import {Component, NgModule} from '@angular/core'; + @Component({ + selector: 'my-component', + template: \` + \` + }) + export class MyComponent { + name = 'World'; + } + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵE(0, 'a'); + $i0$.ɵe(); + } + if (rf & 2) { + $i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, '')); + } + }`; + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect interpolated property binding'); + }); + }); + +});