diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index ccf6bb2eef..cae168aedb 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -1270,12 +1270,10 @@ describe('i18n support in the view compiler', () => { template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵelementStart(0, "span", $_c0$); - $r3$.ɵi18nStart(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$); - $r3$.ɵi18nEnd(); + $r3$.ɵi18n(1, $MSG_EXTERNAL_5295701706185791735$$APP_SPEC_TS_0$); $r3$.ɵelementEnd(); $r3$.ɵelementStart(2, "span", $_c1$); - $r3$.ɵi18nStart(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$); - $r3$.ɵi18nEnd(); + $r3$.ɵi18n(3, $MSG_EXTERNAL_4722270221386399294$$APP_SPEC_TS_2$); $r3$.ɵelementEnd(); } } diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts index f8d06cf28f..e93c462696 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts @@ -395,6 +395,99 @@ describe('compiler compliance: styling', () => { expectEmit(result.source, template, 'Incorrect template'); }); + it('should correctly count the total slots required when style/class bindings include interpolation', + () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component-with-interpolation', + template: \` +
+ \` + }) + export class MyComponentWithInterpolation { + fooId = '123'; + } + + @Component({ + selector: 'my-component-with-muchos-interpolation', + template: \` + + \` + }) + export class MyComponentWithMuchosInterpolation { + fooId = '123'; + fooUsername = 'superfoo'; + } + + @Component({ + selector: 'my-component-without-interpolation', + template: \` + + \` + }) + export class MyComponentWithoutInterpolation { + exp = 'bar'; + } + + @NgModule({declarations: [MyComponentWithInterpolation, MyComponentWithMuchosInterpolation, MyComponentWithoutInterpolation]}) + export class MyModule {} + ` + } + }; + + const template = ` + … + consts: 1, + vars: 1, + template: function MyComponentWithInterpolation_Template(rf, $ctx$) { + if (rf & 1) { + $r3$.ɵelementStart(0, "div"); + $r3$.ɵelementStyling(); + $r3$.ɵelementEnd(); + } + if (rf & 2) { + $r3$.ɵelementStylingMap(0, $r3$.ɵinterpolation1("foo foo-", $ctx$.fooId, "")); + $r3$.ɵelementStylingApply(0); + } + } + … + consts: 1, + vars: 2, + template: function MyComponentWithMuchosInterpolation_Template(rf, $ctx$) { + if (rf & 1) { + $r3$.ɵelementStart(0, "div"); + $r3$.ɵelementStyling(); + $r3$.ɵelementEnd(); + } + if (rf & 2) { + $r3$.ɵelementStylingMap(0, $r3$.ɵinterpolation2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, "")); + $r3$.ɵelementStylingApply(0); + } + } + … + consts: 1, + vars: 0, + template: function MyComponentWithoutInterpolation_Template(rf, $ctx$) { + if (rf & 1) { + $r3$.ɵelementStart(0, "div"); + $r3$.ɵelementStyling(); + $r3$.ɵelementEnd(); + } + if (rf & 2) { + $r3$.ɵelementStylingMap(0, $ctx$.exp); + $r3$.ɵelementStylingApply(0); + } + } + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + it('should place initial, multi, singular and application followed by attribute style instructions in the template code in that order', () => { const files = { @@ -688,8 +781,7 @@ describe('compiler compliance: styling', () => { vars: 2, template: function MyComponent_Template(rf, $ctx$) { if (rf & 1) { - $r3$.ɵelementStart(0, "div", $e0_attrs$); - $r3$.ɵelementEnd(); + $r3$.ɵelement(0, "div", $e0_attrs$); } if (rf & 2) { $r3$.ɵelementAttribute(0, "class", $r3$.ɵbind("round")); diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index f8045eaaef..ceec9614be 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -747,7 +747,7 @@ function createHostBindingsFunction( createStatements.push(createStylingStmt(hostInstruction, bindingContext, bindingFn)); } - if (styleBuilder.hasBindingsOrInitialValues()) { + if (styleBuilder.hasBindings) { // singular style/class bindings (things like `[style.prop]` and `[class.name]`) // MUST be registered on a given element within the component/directive // templateFn/hostBindingsFn functions. The instruction below will figure out diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index 9eab564f8e..0b2c322f91 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -7,7 +7,7 @@ */ import {ConstantPool} from '../../constant_pool'; import {AttributeMarker} from '../../core'; -import {AST, BindingType} from '../../expression_parser/ast'; +import {AST, BindingType, Interpolation} from '../../expression_parser/ast'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; import * as t from '../r3_ast'; @@ -23,6 +23,7 @@ import {ValueConverter} from './template'; export interface Instruction { sourceSpan: ParseSourceSpan|null; reference: o.ExternalReference; + allocateBindingSlots: number; buildParams(convertFn: (value: any) => o.Expression): o.Expression[]; } @@ -36,7 +37,6 @@ interface BoundStylingEntry { value: AST; } - /** * Produces creation/update instructions for all styling bindings (class and style) * @@ -73,7 +73,7 @@ export class StylingBuilder { * Whether or not there are any styling bindings present * (i.e. `[style]`, `[class]`, `[style.prop]` or `[class.name]`) */ - private _hasBindings = false; + public hasBindings = false; /** the input for [class] (if it exists) */ private _classMapInput: BoundStylingEntry|null = null; @@ -110,8 +110,6 @@ export class StylingBuilder { constructor(private _elementIndexExpr: o.Expression, private _directiveExpr: o.Expression|null) {} - hasBindingsOrInitialValues() { return this._hasBindings || this._hasInitialValues; } - /** * Registers a given input to the styling builder to be later used when producing AOT code. * @@ -158,7 +156,7 @@ export class StylingBuilder { this._styleMapInput = entry; } this._lastStylingInput = entry; - this._hasBindings = true; + this.hasBindings = true; return entry; } @@ -172,7 +170,7 @@ export class StylingBuilder { this._classMapInput = entry; } this._lastStylingInput = entry; - this._hasBindings = true; + this.hasBindings = true; return entry; } @@ -235,6 +233,7 @@ export class StylingBuilder { return { sourceSpan, reference: R3.elementHostAttrs, + allocateBindingSlots: 0, buildParams: () => { this.populateInitialStylingAttrs(attrs); return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)]; @@ -252,9 +251,10 @@ export class StylingBuilder { */ buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): Instruction|null { - if (this._hasBindings) { + if (this.hasBindings) { return { sourceSpan, + allocateBindingSlots: 0, reference: R3.elementStyling, buildParams: () => { // a string array of every style-based binding @@ -315,18 +315,27 @@ export class StylingBuilder { buildElementStylingMapInstruction(valueConverter: ValueConverter): Instruction|null { if (this._classMapInput || this._styleMapInput) { const stylingInput = this._classMapInput ! || this._styleMapInput !; + let totalBindingSlotsRequired = 0; // these values must be outside of the update block so that they can // be evaluted (the AST visit call) during creation time so that any // pipes can be picked up in time before the template is built const mapBasedClassValue = this._classMapInput ? this._classMapInput.value.visit(valueConverter) : null; + if (mapBasedClassValue instanceof Interpolation) { + totalBindingSlotsRequired += mapBasedClassValue.expressions.length; + } + const mapBasedStyleValue = this._styleMapInput ? this._styleMapInput.value.visit(valueConverter) : null; + if (mapBasedStyleValue instanceof Interpolation) { + totalBindingSlotsRequired += mapBasedStyleValue.expressions.length; + } return { sourceSpan: stylingInput.sourceSpan, reference: R3.elementStylingMap, + allocateBindingSlots: totalBindingSlotsRequired, buildParams: (convertFn: (value: any) => o.Expression) => { const params: o.Expression[] = [this._elementIndexExpr]; @@ -356,12 +365,14 @@ export class StylingBuilder { private _buildSingleInputs( reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map