From 02491a6ce8140788aff87c38cea3571cfb0ebc6e Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 27 Jun 2019 18:57:09 +0200 Subject: [PATCH] refactor(ivy): move classMap interpolation logic internally (#31211) Adds the new `classMapInterpolate1` through `classMapInterpolate8` instructions which handle interpolations inside the `class` attribute and moves the interpolation logic internally. This allows us to remove the `interpolationX` instructions in a follow-up PR. These changes also add an error if an interpolation is encountered inside a `style` tag (e.g. `style="width: {{value}}"`). Up until now this would actually generate valid instructions, because `styleMap` goes through the same code path as `classMap` which does support interpolation. At runtime, however, `styleMap` would set invalid styles that look like `
`. In `ViewEngine` interpolations inside `style` weren't supported either, however there we'd output invalid styles like `
`, even if the content was trusted. PR Close #31211 --- .../r3_view_compiler_styling_spec.ts | 237 +++++++++- .../compiler/src/render3/r3_identifiers.ts | 38 ++ .../compiler/src/render3/view/compiler.ts | 8 +- .../src/render3/view/styling_builder.ts | 138 ++++-- .../compiler/src/render3/view/template.ts | 67 +-- packages/compiler/src/render3/view/util.ts | 18 + .../core/src/core_render3_private_export.ts | 18 + packages/core/src/render3/index.ts | 21 + packages/core/src/render3/instructions/all.ts | 2 + .../instructions/class_map_interpolation.ts | 353 +++++++++++++++ .../instructions/style_prop_interpolation.ts | 425 ++++++++++++++++++ .../core/src/render3/instructions/styling.ts | 4 +- packages/core/src/render3/jit/environment.ts | 18 + packages/core/test/acceptance/styling_spec.ts | 173 +++++++ tools/public_api_guard/core/core.d.ts | 38 +- 15 files changed, 1469 insertions(+), 89 deletions(-) create mode 100644 packages/core/src/render3/instructions/class_map_interpolation.ts create mode 100644 packages/core/src/render3/instructions/style_prop_interpolation.ts 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 9370eac221..4f0c9d05d6 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 @@ -454,7 +454,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵclassMap($r3$.ɵɵinterpolation1("foo foo-", $ctx$.fooId, "")); + $r3$.ɵɵclassMapInterpolate1("foo foo-", $ctx$.fooId, ""); $r3$.ɵɵstylingApply(); } } @@ -468,7 +468,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵclassMap($r3$.ɵɵinterpolation2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, "")); + $r3$.ɵɵclassMapInterpolate2("foo foo-", $ctx$.fooId, "-", $ctx$.fooUsername, ""); $r3$.ɵɵstylingApply(); } } @@ -1340,6 +1340,239 @@ describe('compiler compliance: styling', () => { }); }); + describe('interpolations', () => { + it('should generate the proper update instructions for interpolated classes', () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: \` +
+
+
+
+
+
+
+
+
+
+ \` + }) + export class MyComponent { + } + ` + } + }; + + const template = ` + … + if (rf & 2) { + $r3$.ɵɵclassMapInterpolateV(["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(1); + $r3$.ɵɵclassMapInterpolate8("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(2); + $r3$.ɵɵclassMapInterpolate7("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(3); + $r3$.ɵɵclassMapInterpolate6("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(4); + $r3$.ɵɵclassMapInterpolate5("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(5); + $r3$.ɵɵclassMapInterpolate4("a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(6); + $r3$.ɵɵclassMapInterpolate3("a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(7); + $r3$.ɵɵclassMapInterpolate2("a", ctx.one, "b", ctx.two, "c"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(8); + $r3$.ɵɵclassMapInterpolate1("a", ctx.one, "b"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(9); + $r3$.ɵɵclassMap(ctx.one); + $r3$.ɵɵstylingApply(); + } + … + `; + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect handling of interpolated classes'); + }); + + it('should throw for interpolations inside `style`', () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: '
' + }) + export class MyComponent { + } + ` + } + }; + + expect(() => compile(files, angularFiles)).toThrowError(/Unexpected interpolation/); + }); + + it('should throw for interpolations inside individual class bindings', () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: '
' + }) + export class MyComponent { + } + ` + } + }; + + expect(() => compile(files, angularFiles)).toThrowError(/Unexpected interpolation/); + }); + + it('should generate the proper update instructions for interpolated style properties', () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: \` +
+
+
+
+
+
+
+
+
+
+ \` + }) + export class MyComponent { + } + ` + } + }; + + const template = ` + … + if (rf & 2) { + $r3$.ɵɵstylePropInterpolateV(0, ["a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i", ctx.nine, "j"]); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(1); + $r3$.ɵɵstylePropInterpolate8(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h", ctx.eight, "i"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(2); + $r3$.ɵɵstylePropInterpolate7(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g", ctx.seven, "h"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(3); + $r3$.ɵɵstylePropInterpolate6(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f", ctx.six, "g"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(4); + $r3$.ɵɵstylePropInterpolate5(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e", ctx.five, "f"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(5); + $r3$.ɵɵstylePropInterpolate4(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d", ctx.four, "e"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(6); + $r3$.ɵɵstylePropInterpolate3(0, "a", ctx.one, "b", ctx.two, "c", ctx.three, "d"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(7); + $r3$.ɵɵstylePropInterpolate2(0, "a", ctx.one, "b", ctx.two, "c"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(8); + $r3$.ɵɵstylePropInterpolate1(0, "a", ctx.one, "b"); + $r3$.ɵɵstylingApply(); + $r3$.ɵɵselect(9); + $r3$.ɵɵstyleProp(0, ctx.one); + $r3$.ɵɵstylingApply(); + } + … + `; + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); + }); + + it('should generate update instructions for interpolated style properties with a suffix', + () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: \` +
+ \` + }) + export class MyComponent { + } + ` + } + }; + + const template = ` + … + if (rf & 2) { + $r3$.ɵɵstylePropInterpolate2(0, "a", ctx.one, "b", ctx.two, "c", "px"); + $r3$.ɵɵstylingApply(); + } + … + `; + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); + }); + + it('should generate update instructions for interpolated style properties with !important', + () => { + const files = { + app: { + 'spec.ts': ` + import {Component} from '@angular/core'; + + @Component({ + template: \` +
+ \` + }) + export class MyComponent { + } + ` + } + }; + + const template = ` + … + if (rf & 2) { + $r3$.ɵɵstylePropInterpolate2(0, "a", ctx.one, "b", ctx.two, "c", null, true); + $r3$.ɵɵstylingApply(); + } + … + `; + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect handling of interpolated style properties'); + }); + + }); + it('should count only non-style and non-class host bindings on Components', () => { const files = { app: { diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 4b903693ab..75f8312305 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -72,8 +72,46 @@ export class Identifiers { static classMap: o.ExternalReference = {name: 'ɵɵclassMap', moduleName: CORE}; + static classMapInterpolate1: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate1', moduleName: CORE}; + static classMapInterpolate2: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate2', moduleName: CORE}; + static classMapInterpolate3: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate3', moduleName: CORE}; + static classMapInterpolate4: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate4', moduleName: CORE}; + static classMapInterpolate5: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate5', moduleName: CORE}; + static classMapInterpolate6: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate6', moduleName: CORE}; + static classMapInterpolate7: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate7', moduleName: CORE}; + static classMapInterpolate8: + o.ExternalReference = {name: 'ɵɵclassMapInterpolate8', moduleName: CORE}; + static classMapInterpolateV: + o.ExternalReference = {name: 'ɵɵclassMapInterpolateV', moduleName: CORE}; + static styleProp: o.ExternalReference = {name: 'ɵɵstyleProp', moduleName: CORE}; + static stylePropInterpolate1: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate1', moduleName: CORE}; + static stylePropInterpolate2: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate2', moduleName: CORE}; + static stylePropInterpolate3: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate3', moduleName: CORE}; + static stylePropInterpolate4: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate4', moduleName: CORE}; + static stylePropInterpolate5: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate5', moduleName: CORE}; + static stylePropInterpolate6: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate6', moduleName: CORE}; + static stylePropInterpolate7: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate7', moduleName: CORE}; + static stylePropInterpolate8: + o.ExternalReference = {name: 'ɵɵstylePropInterpolate8', moduleName: CORE}; + static stylePropInterpolateV: + o.ExternalReference = {name: 'ɵɵstylePropInterpolateV', moduleName: CORE}; + static stylingApply: o.ExternalReference = {name: 'ɵɵstylingApply', moduleName: CORE}; static styleSanitizer: o.ExternalReference = {name: 'ɵɵstyleSanitizer', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index f2efe3a0bd..c0b0af36f2 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -12,7 +12,7 @@ import {CompileReflector} from '../../compile_reflector'; import {BindingForm, convertPropertyBinding} from '../../compiler_util/expression_converter'; import {ConstantPool, DefinitionKind} from '../../constant_pool'; import * as core from '../../core'; -import {AST, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast'; +import {AST, Interpolation, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; import {ParseError, ParseSourceSpan, typeSourceSpan} from '../../parse_util'; @@ -28,7 +28,7 @@ import {Render3ParseResult} from '../r3_template_transform'; import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util'; import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from './api'; -import {Instruction, StylingBuilder} from './styling_builder'; +import {StylingBuilder, StylingInstruction} from './styling_builder'; import {BindingScope, TemplateDefinitionBuilder, ValueConverter, makeBindingParser, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template'; import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, chainedInstruction, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util'; @@ -777,8 +777,8 @@ function bindingFn(implicit: any, value: AST) { } function createStylingStmt( - instruction: Instruction, bindingContext: any, bindingFn: Function): o.Statement { - const params = instruction.buildParams(value => bindingFn(bindingContext, value).currValExpr); + instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement { + const params = instruction.params(value => bindingFn(bindingContext, value).currValExpr); return o.importExpr(instruction.reference, null, instruction.sourceSpan) .callFn(params, instruction.sourceSpan) .toStmt(); diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index 3ca675ecaf..26133a1eed 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -11,23 +11,26 @@ import {AST, BindingType, Interpolation} from '../../expression_parser/ast'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; import {isEmptyExpression} from '../../template_parser/template_parser'; +import {error} from '../../util'; import * as t from '../r3_ast'; import {Identifiers as R3} from '../r3_identifiers'; import {parse as parseStyle} from './style_parser'; import {compilerIsNewStylingInUse} from './styling_state'; import {ValueConverter} from './template'; +import {getInterpolationArgsLength} from './util'; const IMPORTANT_FLAG = '!important'; /** * A styling expression summary that is to be processed by the compiler */ -export interface Instruction { +export interface StylingInstruction { sourceSpan: ParseSourceSpan|null; reference: o.ExternalReference; allocateBindingSlots: number; - buildParams(convertFn: (value: any) => o.Expression): o.Expression[]; + supportsInterpolation?: boolean; + params: ((convertFn: (value: any) => o.Expression | o.Expression[]) => o.Expression[]); } /** @@ -261,13 +264,13 @@ export class StylingBuilder { */ buildHostAttrsInstruction( sourceSpan: ParseSourceSpan|null, attrs: o.Expression[], - constantPool: ConstantPool): Instruction|null { + constantPool: ConstantPool): StylingInstruction|null { if (this._directiveExpr && (attrs.length || this._hasInitialValues)) { return { sourceSpan, reference: R3.elementHostAttrs, allocateBindingSlots: 0, - buildParams: () => { + params: () => { // params => elementHostAttrs(attrs) this.populateInitialStylingAttrs(attrs); const attrArray = !attrs.some(attr => attr instanceof o.WrappedNodeExpr) ? @@ -286,14 +289,14 @@ export class StylingBuilder { * The instruction generation code below is used for producing the AOT statement code which is * responsible for registering style/class bindings to an element. */ - buildStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): Instruction - |null { + buildStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool): + StylingInstruction|null { if (this.hasBindings) { return { sourceSpan, allocateBindingSlots: 0, reference: R3.styling, - buildParams: () => { + params: () => { // a string array of every style-based binding const styleBindingProps = this._singleStyleInputs ? this._singleStyleInputs.map(i => o.literal(i.name)) : []; @@ -344,7 +347,7 @@ export class StylingBuilder { * The instruction data will contain all expressions for `classMap` to function * which includes the `[class]` expression params. */ - buildClassMapInstruction(valueConverter: ValueConverter): Instruction|null { + buildClassMapInstruction(valueConverter: ValueConverter): StylingInstruction|null { if (this._classMapInput) { return this._buildMapBasedInstruction(valueConverter, true, this._classMapInput); } @@ -357,7 +360,7 @@ export class StylingBuilder { * The instruction data will contain all expressions for `styleMap` to function * which includes the `[style]` expression params. */ - buildStyleMapInstruction(valueConverter: ValueConverter): Instruction|null { + buildStyleMapInstruction(valueConverter: ValueConverter): StylingInstruction|null { if (this._styleMapInput) { return this._buildMapBasedInstruction(valueConverter, false, this._styleMapInput); } @@ -365,7 +368,8 @@ export class StylingBuilder { } private _buildMapBasedInstruction( - valueConverter: ValueConverter, isClassBased: boolean, stylingInput: BoundStylingEntry) { + valueConverter: ValueConverter, isClassBased: boolean, + stylingInput: BoundStylingEntry): StylingInstruction { let totalBindingSlotsRequired = 0; if (compilerIsNewStylingInUse()) { // the old implementation does not reserve slot values for @@ -377,27 +381,42 @@ export class StylingBuilder { // be evaluated (the AST visit call) during creation time so that any // pipes can be picked up in time before the template is built const mapValue = stylingInput.value.visit(valueConverter); - if (mapValue instanceof Interpolation) { + let reference: o.ExternalReference; + if (mapValue instanceof Interpolation && isClassBased) { totalBindingSlotsRequired += mapValue.expressions.length; + reference = getClassMapInterpolationExpression(mapValue); + } else { + reference = isClassBased ? R3.classMap : R3.styleMap; } - const reference = isClassBased ? R3.classMap : R3.styleMap; return { sourceSpan: stylingInput.sourceSpan, reference, allocateBindingSlots: totalBindingSlotsRequired, - buildParams: (convertFn: (value: any) => o.Expression) => { return [convertFn(mapValue)]; } + supportsInterpolation: isClassBased, + params: (convertFn: (value: any) => o.Expression | o.Expression[]) => { + const convertResult = convertFn(mapValue); + return Array.isArray(convertResult) ? convertResult : [convertResult]; + } }; } private _buildSingleInputs( reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map, - allowUnits: boolean, valueConverter: ValueConverter): Instruction[] { + allowUnits: boolean, valueConverter: ValueConverter, + getInterpolationExpressionFn?: (value: Interpolation) => o.ExternalReference): + StylingInstruction[] { let totalBindingSlotsRequired = 0; return inputs.map(input => { const bindingIndex: number = mapIndex.get(input.name !) !; const value = input.value.visit(valueConverter); - totalBindingSlotsRequired += (value instanceof Interpolation) ? value.expressions.length : 0; + if (value instanceof Interpolation) { + totalBindingSlotsRequired += value.expressions.length; + + if (getInterpolationExpressionFn) { + reference = getInterpolationExpressionFn(value); + } + } if (compilerIsNewStylingInUse()) { // the old implementation does not reserve slot values for // binding entries. The new one does. @@ -405,14 +424,18 @@ export class StylingBuilder { } return { sourceSpan: input.sourceSpan, + supportsInterpolation: !!getInterpolationExpressionFn, allocateBindingSlots: totalBindingSlotsRequired, reference, - buildParams: (convertFn: (value: any) => o.Expression) => { + params: (convertFn: (value: any) => o.Expression | o.Expression[]) => { // min params => stylingProp(elmIndex, bindingIndex, value) // max params => stylingProp(elmIndex, bindingIndex, value, overrideFlag) - const params: o.Expression[] = []; - params.push(o.literal(bindingIndex)); - params.push(convertFn(value)); - + const params: o.Expression[] = [o.literal(bindingIndex)]; + const convertResult = convertFn(value); + if (Array.isArray(convertResult)) { + params.push(...convertResult); + } else { + params.push(convertResult); + } if (allowUnits) { if (input.unit) { params.push(o.literal(input.unit)); @@ -431,7 +454,7 @@ export class StylingBuilder { }); } - private _buildClassInputs(valueConverter: ValueConverter): Instruction[] { + private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] { if (this._singleClassInputs) { return this._buildSingleInputs( R3.classProp, this._singleClassInputs, this._classesIndex, false, valueConverter); @@ -439,29 +462,30 @@ export class StylingBuilder { return []; } - private _buildStyleInputs(valueConverter: ValueConverter): Instruction[] { + private _buildStyleInputs(valueConverter: ValueConverter): StylingInstruction[] { if (this._singleStyleInputs) { return this._buildSingleInputs( - R3.styleProp, this._singleStyleInputs, this._stylesIndex, true, valueConverter); + R3.styleProp, this._singleStyleInputs, this._stylesIndex, true, valueConverter, + getStylePropInterpolationExpression); } return []; } - private _buildApplyFn(): Instruction { + private _buildApplyFn(): StylingInstruction { return { sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null, reference: R3.stylingApply, allocateBindingSlots: 0, - buildParams: () => { return []; } + params: () => { return []; } }; } - private _buildSanitizerFn() { + private _buildSanitizerFn(): StylingInstruction { return { sourceSpan: this._firstStylingInput ? this._firstStylingInput.sourceSpan : null, reference: R3.styleSanitizer, allocateBindingSlots: 0, - buildParams: () => [o.importExpr(R3.defaultStyleSanitizer)] + params: () => [o.importExpr(R3.defaultStyleSanitizer)] }; } @@ -470,7 +494,7 @@ export class StylingBuilder { * into the update block of a template function or a directive hostBindings function. */ buildUpdateLevelInstructions(valueConverter: ValueConverter) { - const instructions: Instruction[] = []; + const instructions: StylingInstruction[] = []; if (this.hasBindings) { if (compilerIsNewStylingInUse() && this._useDefaultSanitizer) { instructions.push(this._buildSanitizerFn()); @@ -548,3 +572,61 @@ export function parseProperty(name: string): return {property, unit, hasOverrideFlag}; } + +/** + * Gets the instruction to generate for an interpolated class map. + * @param interpolation An Interpolation AST + */ +function getClassMapInterpolationExpression(interpolation: Interpolation): o.ExternalReference { + switch (getInterpolationArgsLength(interpolation)) { + case 1: + return R3.classMap; + case 3: + return R3.classMapInterpolate1; + case 5: + return R3.classMapInterpolate2; + case 7: + return R3.classMapInterpolate3; + case 9: + return R3.classMapInterpolate4; + case 11: + return R3.classMapInterpolate5; + case 13: + return R3.classMapInterpolate6; + case 15: + return R3.classMapInterpolate7; + case 17: + return R3.classMapInterpolate8; + default: + return R3.classMapInterpolateV; + } +} + +/** + * Gets the instruction to generate for an interpolated style prop. + * @param interpolation An Interpolation AST + */ +function getStylePropInterpolationExpression(interpolation: Interpolation) { + switch (getInterpolationArgsLength(interpolation)) { + case 1: + return R3.styleProp; + case 3: + return R3.stylePropInterpolate1; + case 5: + return R3.stylePropInterpolate2; + case 7: + return R3.stylePropInterpolate3; + case 9: + return R3.stylePropInterpolate4; + case 11: + return R3.stylePropInterpolate5; + case 13: + return R3.stylePropInterpolate6; + case 15: + return R3.stylePropInterpolate7; + case 17: + return R3.stylePropInterpolate8; + default: + return R3.stylePropInterpolateV; + } +} diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index f9c7bab668..e1c61c7451 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -36,8 +36,8 @@ import {I18nContext} from './i18n/context'; import {I18nMetaVisitor} from './i18n/meta'; import {getSerializedI18nContent} from './i18n/serializer'; import {I18N_ICU_MAPPING_PREFIX, TRANSLATION_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util'; -import {Instruction, StylingBuilder} from './styling_builder'; -import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, chainedInstruction, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util'; +import {StylingBuilder, StylingInstruction} from './styling_builder'; +import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, chainedInstruction, getAttrsForDirectiveMatching, getInterpolationArgsLength, invalid, trimTrailingNulls, unsupported} from './util'; @@ -1067,14 +1067,21 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } private processStylingInstruction( - elementIndex: number, instruction: Instruction|null, createMode: boolean) { + elementIndex: number, instruction: StylingInstruction|null, createMode: boolean) { if (instruction) { - const paramsFn = () => instruction.buildParams(value => this.convertPropertyBinding(value)); if (createMode) { - this.creationInstruction(instruction.sourceSpan, instruction.reference, paramsFn); + this.creationInstruction(instruction.sourceSpan, instruction.reference, () => { + return instruction.params(value => this.convertPropertyBinding(value)) as o.Expression[]; + }); } else { - this.updateInstruction( - elementIndex, instruction.sourceSpan, instruction.reference, paramsFn); + this.updateInstruction(elementIndex, instruction.sourceSpan, instruction.reference, () => { + return instruction + .params(value => { + return (instruction.supportsInterpolation && value instanceof Interpolation) ? + this.getUpdateInstructionArguments(value) : + this.convertPropertyBinding(value); + }) as o.Expression[]; + }); } } } @@ -1150,12 +1157,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } private convertPropertyBinding(value: AST): o.Expression { - const interpolationFn = - value instanceof Interpolation ? interpolate : () => error('Unexpected interpolation'); - const convertedPropertyBinding = convertPropertyBinding( this, this.getImplicitReceiverExpr(), value, this.bindingContext(), BindingForm.TrySimple, - interpolationFn); + () => error('Unexpected interpolation')); const valExpr = convertedPropertyBinding.currValExpr; this._tempVariables.push(...convertedPropertyBinding.stmts); @@ -1751,31 +1755,6 @@ function getNgProjectAsLiteral(attribute: t.TextAttribute): o.Expression[] { return [o.literal(core.AttributeMarker.ProjectAs), asLiteral(parsedR3Selector)]; } -function interpolate(args: o.Expression[]): o.Expression { - args = args.slice(1); // Ignore the length prefix added for render2 - switch (args.length) { - case 3: - return o.importExpr(R3.interpolation1).callFn(args); - case 5: - return o.importExpr(R3.interpolation2).callFn(args); - case 7: - return o.importExpr(R3.interpolation3).callFn(args); - case 9: - return o.importExpr(R3.interpolation4).callFn(args); - case 11: - return o.importExpr(R3.interpolation5).callFn(args); - case 13: - return o.importExpr(R3.interpolation6).callFn(args); - case 15: - return o.importExpr(R3.interpolation7).callFn(args); - case 17: - return o.importExpr(R3.interpolation8).callFn(args); - } - (args.length >= 19 && args.length % 2 == 1) || - error(`Invalid interpolation argument length ${args.length}`); - return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]); -} - /** * Gets the instruction to generate for an interpolated property * @param interpolation An Interpolation AST @@ -1861,22 +1840,6 @@ function getTextInterpolationExpression(interpolation: Interpolation): o.Externa } } -/** - * Gets the number of arguments expected to be passed to a generated instruction in the case of - * interpolation instructions. - * @param interpolation An interpolation ast - */ -function getInterpolationArgsLength(interpolation: Interpolation) { - const {expressions, strings} = interpolation; - if (expressions.length === 1 && strings.length === 2 && strings[0] === '' && strings[1] === '') { - // If the interpolation has one interpolated value, but the prefix and suffix are both empty - // strings, we only pass one argument, to a special instruction like `propertyInterpolate` or - // `textInterpolate`. - return 1; - } else { - return expressions.length + strings.length; - } -} /** * Options that can be used to modify how a template is parsed by `parseTemplate()`. */ diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index 29881deefd..068d09d487 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -7,6 +7,7 @@ */ import {ConstantPool} from '../../constant_pool'; +import {Interpolation} from '../../expression_parser/ast'; import * as o from '../../output/output_ast'; import {ParseSourceSpan} from '../../parse_util'; import {splitAtColon} from '../../util'; @@ -201,3 +202,20 @@ export function chainedInstruction( return expression; } + +/** + * Gets the number of arguments expected to be passed to a generated instruction in the case of + * interpolation instructions. + * @param interpolation An interpolation ast + */ +export function getInterpolationArgsLength(interpolation: Interpolation) { + const {expressions, strings} = interpolation; + if (expressions.length === 1 && strings.length === 2 && strings[0] === '' && strings[1] === '') { + // If the interpolation has one interpolated value, but the prefix and suffix are both empty + // strings, we only pass one argument, to a special instruction like `propertyInterpolate` or + // `textInterpolate`. + return 1; + } else { + return expressions.length + strings.length; + } +} diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index a50a8de594..473d68fae2 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -130,7 +130,25 @@ export { ɵɵstyling, ɵɵstyleMap, ɵɵclassMap, + ɵɵclassMapInterpolate1, + ɵɵclassMapInterpolate2, + ɵɵclassMapInterpolate3, + ɵɵclassMapInterpolate4, + ɵɵclassMapInterpolate5, + ɵɵclassMapInterpolate6, + ɵɵclassMapInterpolate7, + ɵɵclassMapInterpolate8, + ɵɵclassMapInterpolateV, ɵɵstyleProp, + ɵɵstylePropInterpolate1, + ɵɵstylePropInterpolate2, + ɵɵstylePropInterpolate3, + ɵɵstylePropInterpolate4, + ɵɵstylePropInterpolate5, + ɵɵstylePropInterpolate6, + ɵɵstylePropInterpolate7, + ɵɵstylePropInterpolate8, + ɵɵstylePropInterpolateV, ɵɵstylingApply, ɵɵclassProp, ɵɵelementHostAttrs, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 761acb1956..bbdb46ee1c 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -36,6 +36,16 @@ export { ɵɵattributeInterpolateV, ɵɵclassMap, + ɵɵclassMapInterpolate1, + ɵɵclassMapInterpolate2, + ɵɵclassMapInterpolate3, + ɵɵclassMapInterpolate4, + ɵɵclassMapInterpolate5, + ɵɵclassMapInterpolate6, + ɵɵclassMapInterpolate7, + ɵɵclassMapInterpolate8, + ɵɵclassMapInterpolateV, + ɵɵclassProp, ɵɵcomponentHostSyntheticListener, @@ -97,7 +107,18 @@ export { ɵɵselect, ɵɵstyleMap, + ɵɵstyleProp, + ɵɵstylePropInterpolate1, + ɵɵstylePropInterpolate2, + ɵɵstylePropInterpolate3, + ɵɵstylePropInterpolate4, + ɵɵstylePropInterpolate5, + ɵɵstylePropInterpolate6, + ɵɵstylePropInterpolate7, + ɵɵstylePropInterpolate8, + ɵɵstylePropInterpolateV, + ɵɵstyleSanitizer, ɵɵstyling, ɵɵstylingApply, diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index ab291b1cb4..0eb52c5ee2 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -48,3 +48,5 @@ export * from './styling'; export {styleSanitizer as ɵɵstyleSanitizer} from '../styling_next/instructions'; export * from './text'; export * from './text_interpolation'; +export * from './class_map_interpolation'; +export * from './style_prop_interpolation'; diff --git a/packages/core/src/render3/instructions/class_map_interpolation.ts b/packages/core/src/render3/instructions/class_map_interpolation.ts new file mode 100644 index 0000000000..7279919701 --- /dev/null +++ b/packages/core/src/render3/instructions/class_map_interpolation.ts @@ -0,0 +1,353 @@ +/** + * @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 {NO_CHANGE} from '../tokens'; + +import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; +import {ɵɵclassMap} from './styling'; + +/** + * + * Update an interpolated class on an element with single bound value surrounded by text. + * + * Used when the value passed to a property has 1 interpolated value in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate1('prefix', v0, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation1(prefix, v0, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 2 bound values surrounded by text. + * + * Used when the value passed to a property has 2 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate2('prefix', v0, '-', v1, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate2( + prefix: string, v0: any, i0: string, v1: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation2(prefix, v0, i0, v1, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 3 bound values surrounded by text. + * + * Used when the value passed to a property has 3 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate3( + * 'prefix', v0, '-', v1, '-', v2, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate3( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 4 bound values surrounded by text. + * + * Used when the value passed to a property has 4 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate4( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate4( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 5 bound values surrounded by text. + * + * Used when the value passed to a property has 5 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate5( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate5( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 6 bound values surrounded by text. + * + * Used when the value passed to a property has 6 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate6( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate6( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 7 bound values surrounded by text. + * + * Used when the value passed to a property has 7 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate7( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param i5 Static value used for concatenation only. + * @param v6 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate7( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * + * Update an interpolated class on an element with 8 bound values surrounded by text. + * + * Used when the value passed to a property has 8 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolate8( + * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix'); + * ``` + * + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param i5 Static value used for concatenation only. + * @param v6 Value checked for change. + * @param i6 Static value used for concatenation only. + * @param v7 Value checked for change. + * @param suffix Static value used for concatenation only. + * @codeGenApi + */ +export function ɵɵclassMapInterpolate8( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, + suffix: string): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} + +/** + * Update an interpolated class on an element with 8 or more bound values surrounded by text. + * + * Used when the number of interpolated values exceeds 7. + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵclassMapInterpolateV( + * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, + * 'suffix']); + * ``` + *. + * @param values The a collection of values and the strings in-between those values, beginning with + * a string prefix and ending with a string suffix. + * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) + * @codeGenApi + */ +export function ɵɵclassMapInterpolateV(values: any[]): void { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolationV(values); + if (interpolatedValue !== NO_CHANGE) { + ɵɵclassMap(interpolatedValue); + } +} diff --git a/packages/core/src/render3/instructions/style_prop_interpolation.ts b/packages/core/src/render3/instructions/style_prop_interpolation.ts new file mode 100644 index 0000000000..4f67b48136 --- /dev/null +++ b/packages/core/src/render3/instructions/style_prop_interpolation.ts @@ -0,0 +1,425 @@ +/** + * @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 {NO_CHANGE} from '../tokens'; +import {ɵɵinterpolation1, ɵɵinterpolation2, ɵɵinterpolation3, ɵɵinterpolation4, ɵɵinterpolation5, ɵɵinterpolation6, ɵɵinterpolation7, ɵɵinterpolation8, ɵɵinterpolationV} from './interpolation'; +import {TsickleIssue1009} from './shared'; +import {ɵɵstyleProp} from './styling'; + + +/** + * + * Update an interpolated style property on an element with single bound value surrounded by text. + * + * Used when the value passed to a property has 1 interpolated value in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate1(0, 'prefix', v0, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate1( + styleIndex: number, prefix: string, v0: any, suffix: string, valueSuffix?: string | null, + forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation1(prefix, v0, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate1; +} + +/** + * + * Update an interpolated style property on an element with 2 bound values surrounded by text. + * + * Used when the value passed to a property has 2 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate2(0, 'prefix', v0, '-', v1, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate2( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, suffix: string, + valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation2(prefix, v0, i0, v1, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate2; +} + +/** + * + * Update an interpolated style property on an element with 3 bound values surrounded by text. + * + * Used when the value passed to a property has 3 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate3(0, 'prefix', v0, '-', v1, '-', v2, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate3( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation3(prefix, v0, i0, v1, i1, v2, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate3; +} + +/** + * + * Update an interpolated style property on an element with 4 bound values surrounded by text. + * + * Used when the value passed to a property has 4 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate4(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate4( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + i2: string, v3: any, suffix: string, valueSuffix?: string | null, + forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate4; +} + +/** + * + * Update an interpolated style property on an element with 5 bound values surrounded by text. + * + * Used when the value passed to a property has 5 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate5(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate5( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + i2: string, v3: any, i3: string, v4: any, suffix: string, valueSuffix?: string | null, + forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = ɵɵinterpolation5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate5; +} + +/** + * + * Update an interpolated style property on an element with 6 bound values surrounded by text. + * + * Used when the value passed to a property has 6 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate6(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate6( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, + valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate6; +} + +/** + * + * Update an interpolated style property on an element with 7 bound values surrounded by text. + * + * Used when the value passed to a property has 7 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate7( + * 0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param i5 Static value used for concatenation only. + * @param v6 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate7( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, + suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate7; +} + +/** + * + * Update an interpolated style property on an element with 8 bound values surrounded by text. + * + * Used when the value passed to a property has 8 interpolated values in it: + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolate8(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, + * '-', v7, 'suffix'); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`. + * @param prefix Static value used for concatenation only. + * @param v0 Value checked for change. + * @param i0 Static value used for concatenation only. + * @param v1 Value checked for change. + * @param i1 Static value used for concatenation only. + * @param v2 Value checked for change. + * @param i2 Static value used for concatenation only. + * @param v3 Value checked for change. + * @param i3 Static value used for concatenation only. + * @param v4 Value checked for change. + * @param i4 Static value used for concatenation only. + * @param v5 Value checked for change. + * @param i5 Static value used for concatenation only. + * @param v6 Value checked for change. + * @param i6 Static value used for concatenation only. + * @param v7 Value checked for change. + * @param suffix Static value used for concatenation only. + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolate8( + styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, + i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, + v7: any, suffix: string, valueSuffix?: string | null, + forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolatedValue = + ɵɵinterpolation8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + if (interpolatedValue !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolatedValue as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolate8; +} + +/** + * Update an interpolated style property on an element with 8 or more bound values surrounded by + * text. + * + * Used when the number of interpolated values exceeds 7. + * + * ```html + *
+ *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstylePropInterpolateV( + * 0, ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9, + * 'suffix']); + * ``` + * + * @param styleIndex Index of style to update. This index value refers to the + * index of the style in the style bindings array that was passed into + * `styling`.. + * @param values The a collection of values and the strings in-between those values, beginning with + * a string prefix and ending with a string suffix. + * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`) + * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`. + * @param forceOverride Whether or not to update the styling value immediately. + * @returns itself, so that it may be chained. + * @codeGenApi + */ +export function ɵɵstylePropInterpolateV( + styleIndex: number, values: any[], valueSuffix?: string | null, + forceOverride?: boolean): TsickleIssue1009 { + // TODO(FW-1340): Refactor to remove the use of other instructions here. + const interpolated = ɵɵinterpolationV(values); + if (interpolated !== NO_CHANGE) { + ɵɵstyleProp(styleIndex, interpolated as string, valueSuffix, forceOverride); + } + return ɵɵstylePropInterpolateV; +} diff --git a/packages/core/src/render3/instructions/styling.ts b/packages/core/src/render3/instructions/styling.ts index 9fc8aec6b1..fb557dea0e 100644 --- a/packages/core/src/render3/instructions/styling.ts +++ b/packages/core/src/render3/instructions/styling.ts @@ -310,7 +310,7 @@ export function ɵɵstyleMap(styles: {[styleName: string]: any} | NO_CHANGE | nu * * @codeGenApi */ -export function ɵɵclassMap(classes: {[styleName: string]: any} | NO_CHANGE | string | null): void { +export function ɵɵclassMap(classes: {[styleName: string]: any} | string | null): void { const index = getSelectedIndex(); const lView = getLView(); const stylingContext = getStylingContext(index, lView); @@ -323,7 +323,7 @@ export function ɵɵclassMap(classes: {[styleName: string]: any} | NO_CHANGE | s // inputs are only evaluated from a template binding into a directive, therefore, // there should not be a situation where a directive host bindings function // evaluates the inputs (this should only happen in the template function) - if (hasClassInput(tNode) && classes !== NO_CHANGE) { + if (hasClassInput(tNode)) { const initialClasses = getInitialClassNameValue(stylingContext); const classInputVal = (initialClasses.length ? (initialClasses + ' ') : '') + forceClassesAsString(classes); diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 8eb0b2350f..5b3ebb13b3 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -115,9 +115,27 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵreference': r3.ɵɵreference, 'ɵɵelementHostAttrs': r3.ɵɵelementHostAttrs, 'ɵɵclassMap': r3.ɵɵclassMap, + 'ɵɵclassMapInterpolate1': r3.ɵɵclassMapInterpolate1, + 'ɵɵclassMapInterpolate2': r3.ɵɵclassMapInterpolate2, + 'ɵɵclassMapInterpolate3': r3.ɵɵclassMapInterpolate3, + 'ɵɵclassMapInterpolate4': r3.ɵɵclassMapInterpolate4, + 'ɵɵclassMapInterpolate5': r3.ɵɵclassMapInterpolate5, + 'ɵɵclassMapInterpolate6': r3.ɵɵclassMapInterpolate6, + 'ɵɵclassMapInterpolate7': r3.ɵɵclassMapInterpolate7, + 'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8, + 'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV, 'ɵɵstyling': r3.ɵɵstyling, 'ɵɵstyleMap': r3.ɵɵstyleMap, 'ɵɵstyleProp': r3.ɵɵstyleProp, + 'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1, + 'ɵɵstylePropInterpolate2': r3.ɵɵstylePropInterpolate2, + 'ɵɵstylePropInterpolate3': r3.ɵɵstylePropInterpolate3, + 'ɵɵstylePropInterpolate4': r3.ɵɵstylePropInterpolate4, + 'ɵɵstylePropInterpolate5': r3.ɵɵstylePropInterpolate5, + 'ɵɵstylePropInterpolate6': r3.ɵɵstylePropInterpolate6, + 'ɵɵstylePropInterpolate7': r3.ɵɵstylePropInterpolate7, + 'ɵɵstylePropInterpolate8': r3.ɵɵstylePropInterpolate8, + 'ɵɵstylePropInterpolateV': r3.ɵɵstylePropInterpolateV, 'ɵɵstyleSanitizer': r3.ɵɵstyleSanitizer, 'ɵɵstylingApply': r3.ɵɵstylingApply, 'ɵɵclassProp': r3.ɵɵclassProp, diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 4b66286004..6fd6d69156 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -200,4 +200,177 @@ describe('styling', () => { // that we use to run tests doesn't support `clip-path` in `CSSStyleDeclaration`. expect(html).toMatch(/style=["|']clip-path:\s*url\(.*#test.*\)/); }); + + it('should support interpolations inside a class binding', () => { + @Component({ + template: ` +
+
+
+
+
+
+
+
+
+
+ ` + }) + class Cmp { + one = 'one'; + two = 'two'; + three = 'three'; + four = 'four'; + five = 'five'; + six = 'six'; + seven = 'seven'; + eight = 'eight'; + nine = 'nine'; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + const instance = fixture.componentInstance; + fixture.detectChanges(); + + const divs = fixture.nativeElement.querySelectorAll('div'); + + expect(divs[0].getAttribute('class')).toBe('aonebtwocthreedfourefivefsixgsevenheightininej'); + expect(divs[1].getAttribute('class')).toBe('aonebtwocthreedfourefivefsixgsevenheighti'); + expect(divs[2].getAttribute('class')).toBe('aonebtwocthreedfourefivefsixgsevenh'); + expect(divs[3].getAttribute('class')).toBe('aonebtwocthreedfourefivefsixg'); + expect(divs[4].getAttribute('class')).toBe('aonebtwocthreedfourefivef'); + expect(divs[5].getAttribute('class')).toBe('aonebtwocthreedfoure'); + expect(divs[6].getAttribute('class')).toBe('aonebtwocthreed'); + expect(divs[7].getAttribute('class')).toBe('aonebtwoc'); + expect(divs[8].getAttribute('class')).toBe('aoneb'); + expect(divs[9].getAttribute('class')).toBe('one'); + + instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = + instance.seven = instance.eight = instance.nine = ''; + fixture.detectChanges(); + + expect(divs[0].getAttribute('class')).toBe('abcdefghij'); + expect(divs[1].getAttribute('class')).toBe('abcdefghi'); + expect(divs[2].getAttribute('class')).toBe('abcdefgh'); + expect(divs[3].getAttribute('class')).toBe('abcdefg'); + expect(divs[4].getAttribute('class')).toBe('abcdef'); + expect(divs[5].getAttribute('class')).toBe('abcde'); + expect(divs[6].getAttribute('class')).toBe('abcd'); + expect(divs[7].getAttribute('class')).toBe('abc'); + expect(divs[8].getAttribute('class')).toBe('ab'); + expect(divs[9].getAttribute('class')).toBeFalsy(); + }); + + it('should support interpolations inside a class binding when other classes are present', () => { + @Component({template: '
'}) + class Cmp { + one = 'one'; + two = 'two'; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + const classList = fixture.nativeElement.querySelector('div').classList; + + expect(classList).toContain('zero'); + expect(classList).toContain('i-one'); + expect(classList).toContain('two'); + expect(classList).toContain('three'); + + fixture.componentInstance.one = fixture.componentInstance.two = ''; + fixture.detectChanges(); + + expect(classList).toContain('zero'); + expect(classList).toContain('i-'); + expect(classList).toContain('three'); + expect(classList).not.toContain('i-one'); + expect(classList).not.toContain('two'); + }); + + it('should support interpolations inside a style property binding', () => { + @Component({ + template: ` +
+
+
+
+
+
+
+
+
+
+ ` + }) + class Cmp { + singleBinding: string|null = '1337px'; + one = 1; + two = 2; + three = 3; + four = 4; + five = 5; + six = 6; + seven = 7; + eight = 8; + nine = 9; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + const instance = fixture.componentInstance; + fixture.detectChanges(); + + const divs: NodeListOf = fixture.nativeElement.querySelectorAll('div'); + + expect(divs[0].style.fontFamily).toBe('f123456789'); + expect(divs[1].style.fontFamily).toBe('f12345678'); + expect(divs[2].style.fontFamily).toBe('f1234567'); + expect(divs[3].style.fontFamily).toBe('f123456'); + expect(divs[4].style.fontFamily).toBe('f12345'); + expect(divs[5].style.fontFamily).toBe('f1234'); + expect(divs[6].style.fontFamily).toBe('f123'); + expect(divs[7].style.fontFamily).toBe('f12'); + expect(divs[8].style.fontFamily).toBe('f1'); + expect(divs[9].style.width).toBe('1337px'); + + instance.singleBinding = null; + instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = + instance.seven = instance.eight = instance.nine = 1; + fixture.detectChanges(); + + expect(divs[0].style.fontFamily).toBe('f111111111'); + expect(divs[1].style.fontFamily).toBe('f11111111'); + expect(divs[2].style.fontFamily).toBe('f1111111'); + expect(divs[3].style.fontFamily).toBe('f111111'); + expect(divs[4].style.fontFamily).toBe('f11111'); + expect(divs[5].style.fontFamily).toBe('f1111'); + expect(divs[6].style.fontFamily).toBe('f111'); + expect(divs[7].style.fontFamily).toBe('f11'); + expect(divs[8].style.fontFamily).toBe('f1'); + expect(divs[9].style.width).toBeFalsy(); + }); + + it('should support interpolations when a style property has a unit suffix', () => { + @Component({template: '
'}) + class Cmp { + one = 1; + three = 3; + } + + TestBed.configureTestingModule({declarations: [Cmp]}); + const fixture = TestBed.createComponent(Cmp); + fixture.detectChanges(); + const div = fixture.nativeElement.querySelector('div'); + + expect(div.style.width).toBe('1337px'); + + fixture.componentInstance.one = 2; + fixture.componentInstance.three = 6; + fixture.detectChanges(); + + expect(div.style.width).toBe('2667px'); + }); + }); diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index f42053504b..ce8cd741cb 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -702,7 +702,25 @@ export interface ɵɵBaseDef { export declare function ɵɵclassMap(classes: { [styleName: string]: any; -} | NO_CHANGE | string | null): void; +} | string | null): void; + +export declare function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate5(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate6(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate7(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolate8(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string): void; + +export declare function ɵɵclassMapInterpolateV(values: any[]): void; export declare function ɵɵclassProp(classIndex: number, value: boolean | PlayerFactory, forceOverride?: boolean): void; @@ -1043,6 +1061,24 @@ export declare function ɵɵstyleMap(styles: { export declare function ɵɵstyleProp(styleIndex: number, value: string | number | String | PlayerFactory | null, suffix?: string | null, forceOverride?: boolean): void; +export declare function ɵɵstylePropInterpolate1(styleIndex: number, prefix: string, v0: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate2(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate3(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate4(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate5(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate6(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate7(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolate8(styleIndex: number, prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, i3: string, v4: any, i4: string, v5: any, i5: string, v6: any, i6: string, v7: any, suffix: string, valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + +export declare function ɵɵstylePropInterpolateV(styleIndex: number, values: any[], valueSuffix?: string | null, forceOverride?: boolean): TsickleIssue1009; + export declare function ɵɵstyling(classBindingNames?: string[] | null, styleBindingNames?: string[] | null, styleSanitizer?: StyleSanitizeFn | null): void; export declare function ɵɵstylingApply(): void;