From 2562a3b1b054a94089834e0e0c69b59b55de9a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mi=C5=A1ko=20Hevery?= Date: Wed, 27 Nov 2019 16:57:14 -0800 Subject: [PATCH] fix(ivy): Add `style="{{exp}}"` based interpolation (#34202) Fixes #33575 Add support for interpolation in styles as shown: ```
``` PR Close #34202 --- .../r3_view_compiler_styling_spec.ts | 180 +++++++-- .../compiler/src/render3/r3_identifiers.ts | 19 + .../src/render3/view/styling_builder.ts | 44 ++- .../core/src/core_render3_private_export.ts | 9 + packages/core/src/render3/index.ts | 9 + packages/core/src/render3/instructions/all.ts | 1 + .../instructions/attribute_interpolation.ts | 6 +- .../instructions/class_map_interpolation.ts | 6 +- .../instructions/property_interpolation.ts | 6 +- .../instructions/style_map_interpolation.ts | 343 ++++++++++++++++++ .../instructions/style_prop_interpolation.ts | 6 +- .../core/src/render3/instructions/styling.ts | 2 +- .../instructions/text_interpolation.ts | 2 +- packages/core/src/render3/jit/environment.ts | 9 + packages/core/test/acceptance/styling_spec.ts | 101 +++++- tools/public_api_guard/core/core.d.ts | 18 + 16 files changed, 689 insertions(+), 72 deletions(-) create mode 100644 packages/core/src/render3/instructions/style_map_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 2b8b87b877..64a5aaaa40 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 @@ -377,7 +377,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelement(0, "div"); } if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap($ctx$.myStyleExp); } } `; @@ -510,7 +510,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelement(0, "div", 0); } if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap($ctx$.myStyleExp); $r3$.ɵɵstyleProp("width", $ctx$.myWidth)("height", $ctx$.myHeight); $r3$.ɵɵattribute("style", "border-width: 10px", $r3$.ɵɵsanitizeStyle); } @@ -813,7 +813,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelement(0, "div"); } if (rf & 2) { - $r3$.ɵɵstyleMap($ctx$.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap($ctx$.myStyleExp); $r3$.ɵɵclassMap($ctx$.myClassExp); } } @@ -854,7 +854,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 4, $ctx$.myStyleExp), $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind1(1, 4, $ctx$.myStyleExp)); $r3$.ɵɵclassMap($r3$.ɵɵpipeBind1(2, 6, $ctx$.myClassExp)); } } @@ -906,7 +906,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 11, $ctx$.myStyleExp, 1000), $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap($r3$.ɵɵpipeBind2(1, 11, $ctx$.myStyleExp, 1000)); $r3$.ɵɵclassMap($r3$.ɵɵpureFunction0(23, _c0)); $r3$.ɵɵstyleProp("bar", $r3$.ɵɵpipeBind2(2, 14, $ctx$.barExp, 3000))("baz", $r3$.ɵɵpipeBind2(3, 17, $ctx$.bazExp, 4000)); $r3$.ɵɵclassProp("foo", $r3$.ɵɵpipeBind2(4, 20, $ctx$.fooExp, 2000)); @@ -1009,7 +1009,7 @@ describe('compiler compliance: styling', () => { hostVars: 8, hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵclassMap(ctx.myClass); $r3$.ɵɵstyleProp("color", ctx.myColorProp); $r3$.ɵɵclassProp("foo", ctx.myFooClass); @@ -1064,7 +1064,7 @@ describe('compiler compliance: styling', () => { hostVars: 12, hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵclassMap(ctx.myClasses); $r3$.ɵɵstyleProp("height", ctx.myHeightProp, "pt")("width", ctx.myWidthProp); $r3$.ɵɵclassProp("bar", ctx.myBarClass)("foo", ctx.myFooClass); @@ -1121,7 +1121,7 @@ describe('compiler compliance: styling', () => { $r3$.ɵɵelement(0, "div"); } if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.myStyleExp); $r3$.ɵɵclassMap(ctx.myClassExp); $r3$.ɵɵstyleProp("height", ctx.myHeightExp); $r3$.ɵɵclassProp("bar", ctx.myBarClassExp); @@ -1133,7 +1133,7 @@ describe('compiler compliance: styling', () => { hostVars: 8, hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.myStyleExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.myStyleExp); $r3$.ɵɵclassMap(ctx.myClassExp); $r3$.ɵɵstyleProp("width", ctx.myWidthExp); $r3$.ɵɵclassProp("foo", ctx.myFooClassExp); @@ -1146,6 +1146,146 @@ describe('compiler compliance: styling', () => { expectEmit(result.source, template, 'Incorrect template'); }); + it('should support class interpolation', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule, HostBinding} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \` +
+
+
+
+
+
+
+
+
+ \`, + }) + export class MyComponent { + p1 = 100; + p2 = 100; + p3 = 100; + p4 = 100; + p5 = 100; + p6 = 100; + p6 = 100; + p7 = 100; + p8 = 100; + p9 = 100; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + function MyComponent_Template(rf, ctx) { + if (rf & 1) { + … + } + if (rf & 2) { + $r3$.ɵɵclassMapInterpolate1("A", ctx.p1, "B"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate2("A", ctx.p1, "B", ctx.p2, "C"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate3("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate4("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate5("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate6("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate7("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolate8("A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H", ctx.p8, "I"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵclassMapInterpolateV(["A", ctx.p1, "B", ctx.p2, "C", ctx.p3, "D", ctx.p4, "E", ctx.p5, "F", ctx.p6, "G", ctx.p7, "H", ctx.p8, "I", ctx.p9, "J"]); + } + }, + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + + it('should support style interpolation', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule, HostBinding} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \` +
+
+
+
+
+
+
+
+
+ \`, + }) + export class MyComponent { + p1 = 100; + p2 = 100; + p3 = 100; + p4 = 100; + p5 = 100; + p6 = 100; + p6 = 100; + p7 = 100; + p8 = 100; + p9 = 100; + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + function MyComponent_Template(rf, ctx) { + if (rf & 1) { + … + } + if (rf & 2) { + $r3$.ɵɵstyleMapInterpolate1("p1:", ctx.p1, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate2("p1:", ctx.p1, ";p2:", ctx.p2, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate3("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate4("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate5("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate6("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate7("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolate8("p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";p8:", ctx.p8, ";"); + $r3$.ɵɵadvance(1); + $r3$.ɵɵstyleMapInterpolateV(["p1:", ctx.p1, ";p2:", ctx.p2, ";p3:", ctx.p3, ";p4:", ctx.p4, ";p5:", ctx.p5, ";p6:", ctx.p6, ";p7:", ctx.p7, ";p8:", ctx.p8, ";p9:", ctx.p9, ";"]); + } + }, + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + it('should generate styling instructions for multiple directives that contain host binding definitions', () => { const files = { @@ -1281,24 +1421,6 @@ describe('compiler compliance: styling', () => { 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: { @@ -1864,7 +1986,7 @@ describe('compiler compliance: styling', () => { hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { $r3$.ɵɵhostProperty("id", ctx.id)("title", ctx.title); - $r3$.ɵɵstyleMap(ctx.myStyle, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.myStyle); $r3$.ɵɵclassMap(ctx.myClass); } } @@ -1972,7 +2094,7 @@ describe('compiler compliance: styling', () => { template: function MyAppComp_Template(rf, ctx) { … if (rf & 2) { - $r3$.ɵɵstyleMap(ctx.mapExp, $r3$.ɵɵdefaultStyleSanitizer); + $r3$.ɵɵstyleMap(ctx.mapExp); } … } diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index c0d70a74cb..4d27564bd2 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -71,6 +71,25 @@ export class Identifiers { static styleMap: o.ExternalReference = {name: 'ɵɵstyleMap', moduleName: CORE}; + static styleMapInterpolate1: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate1', moduleName: CORE}; + static styleMapInterpolate2: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate2', moduleName: CORE}; + static styleMapInterpolate3: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate3', moduleName: CORE}; + static styleMapInterpolate4: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate4', moduleName: CORE}; + static styleMapInterpolate5: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate5', moduleName: CORE}; + static styleMapInterpolate6: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate6', moduleName: CORE}; + static styleMapInterpolate7: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate7', moduleName: CORE}; + static styleMapInterpolate8: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolate8', moduleName: CORE}; + static styleMapInterpolateV: + o.ExternalReference = {name: 'ɵɵstyleMapInterpolateV', moduleName: CORE}; + static classMap: o.ExternalReference = {name: 'ɵɵclassMap', moduleName: CORE}; static classMapInterpolate1: diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index 9ac6d594f1..46252453c8 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -81,7 +81,7 @@ export interface StylingInstruction { export interface StylingInstructionCall { sourceSpan: ParseSourceSpan|null; - supportsInterpolation?: boolean; + supportsInterpolation: boolean; allocateBindingSlots: number; params: ((convertFn: (value: any) => o.Expression | o.Expression[]) => o.Expression[]); } @@ -372,9 +372,10 @@ export class StylingBuilder { // pipes can be picked up in time before the template is built const mapValue = stylingInput.value.visit(valueConverter); let reference: o.ExternalReference; - if (mapValue instanceof Interpolation && isClassBased) { + if (mapValue instanceof Interpolation) { totalBindingSlotsRequired += mapValue.expressions.length; - reference = getClassMapInterpolationExpression(mapValue); + reference = isClassBased ? getClassMapInterpolationExpression(mapValue) : + getStyleMapInterpolationExpression(mapValue); } else { reference = isClassBased ? R3.classMap : R3.styleMap; } @@ -382,18 +383,12 @@ export class StylingBuilder { return { reference, calls: [{ - supportsInterpolation: isClassBased, + supportsInterpolation: true, sourceSpan: stylingInput.sourceSpan, allocateBindingSlots: totalBindingSlotsRequired, params: (convertFn: (value: any) => o.Expression | o.Expression[]) => { const convertResult = convertFn(mapValue); const params = Array.isArray(convertResult) ? convertResult : [convertResult]; - - // [style] instructions will sanitize all their values. For this reason we - // need to include the sanitizer as a param. - if (!isClassBased) { - params.push(o.importExpr(R3.defaultStyleSanitizer)); - } return params; } }] @@ -588,6 +583,35 @@ function getClassMapInterpolationExpression(interpolation: Interpolation): o.Ext } } +/** + * Gets the instruction to generate for an interpolated style map. + * @param interpolation An Interpolation AST + */ +function getStyleMapInterpolationExpression(interpolation: Interpolation): o.ExternalReference { + switch (getInterpolationArgsLength(interpolation)) { + case 1: + return R3.styleMap; + case 3: + return R3.styleMapInterpolate1; + case 5: + return R3.styleMapInterpolate2; + case 7: + return R3.styleMapInterpolate3; + case 9: + return R3.styleMapInterpolate4; + case 11: + return R3.styleMapInterpolate5; + case 13: + return R3.styleMapInterpolate6; + case 15: + return R3.styleMapInterpolate7; + case 17: + return R3.styleMapInterpolate8; + default: + return R3.styleMapInterpolateV; + } +} + /** * Gets the instruction to generate for an interpolated style prop. * @param interpolation An Interpolation AST diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 056001cf85..3e4a0cda93 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -121,6 +121,15 @@ export { ɵɵelementContainerEnd, ɵɵelementContainer, ɵɵstyleMap, + ɵɵstyleMapInterpolate1, + ɵɵstyleMapInterpolate2, + ɵɵstyleMapInterpolate3, + ɵɵstyleMapInterpolate4, + ɵɵstyleMapInterpolate5, + ɵɵstyleMapInterpolate6, + ɵɵstyleMapInterpolate7, + ɵɵstyleMapInterpolate8, + ɵɵstyleMapInterpolateV, ɵɵstyleSanitizer, ɵɵclassMap, ɵɵclassMapInterpolate1, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index e896ceee85..d88585000e 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -100,6 +100,15 @@ export { ɵɵselect, ɵɵadvance, ɵɵstyleMap, + ɵɵstyleMapInterpolate1, + ɵɵstyleMapInterpolate2, + ɵɵstyleMapInterpolate3, + ɵɵstyleMapInterpolate4, + ɵɵstyleMapInterpolate5, + ɵɵstyleMapInterpolate6, + ɵɵstyleMapInterpolate7, + ɵɵstyleMapInterpolate8, + ɵɵstyleMapInterpolateV, ɵɵstyleProp, ɵɵstylePropInterpolate1, diff --git a/packages/core/src/render3/instructions/all.ts b/packages/core/src/render3/instructions/all.ts index 125c077bd4..6e37202846 100644 --- a/packages/core/src/render3/instructions/all.ts +++ b/packages/core/src/render3/instructions/all.ts @@ -46,5 +46,6 @@ export * from './styling'; export * from './text'; export * from './text_interpolation'; export * from './class_map_interpolation'; +export * from './style_map_interpolation'; export * from './style_prop_interpolation'; export * from './host_property'; diff --git a/packages/core/src/render3/instructions/attribute_interpolation.ts b/packages/core/src/render3/instructions/attribute_interpolation.ts index 1f83b7e817..1d6c6833af 100644 --- a/packages/core/src/render3/instructions/attribute_interpolation.ts +++ b/packages/core/src/render3/instructions/attribute_interpolation.ts @@ -413,9 +413,9 @@ export function ɵɵattributeInterpolate8( } /** - * Update an interpolated attribute on an element with 8 or more bound values surrounded by text. + * Update an interpolated attribute on an element with 9 or more bound values surrounded by text. * - * Used when the number of interpolated values exceeds 7. + * Used when the number of interpolated values exceeds 8. * * ```html *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstyleMapInterpolate1('key: ', 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 ɵɵstyleMapInterpolate1(prefix: string, v0: any, suffix: string): void { + const lView = getLView(); + const interpolatedValue = interpolation1(lView, prefix, v0, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate2('key: ', v0, '; key1: ', 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 ɵɵstyleMapInterpolate2( + prefix: string, v0: any, i0: string, v1: any, suffix: string): void { + const lView = getLView(); + const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate3( + * 'key: ', v0, '; key1: ', v1, '; key2: ', 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 ɵɵstyleMapInterpolate3( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void { + const lView = getLView(); + const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate4( + * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', 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 ɵɵstyleMapInterpolate4( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + suffix: string): void { + const lView = getLView(); + const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate5( + * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', 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 ɵɵstyleMapInterpolate5( + prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, + i3: string, v4: any, suffix: string): void { + const lView = getLView(); + const interpolatedValue = + interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate6( + * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', 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 ɵɵstyleMapInterpolate6( + 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 { + const lView = getLView(); + const interpolatedValue = + interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate7( + * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, + * '; key6: ', 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 ɵɵstyleMapInterpolate7( + 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 { + const lView = getLView(); + const interpolatedValue = + interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * + * Update an interpolated style 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 + * ɵɵstyleMapInterpolate8( + * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, + * '; key6: ', v6, '; key7: ', 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 ɵɵstyleMapInterpolate8( + 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 { + const lView = getLView(); + const interpolatedValue = interpolation8( + lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); + ɵɵstyleMap(interpolatedValue); +} + +/** + * Update an interpolated style on an element with 9 or more bound values surrounded by text. + * + * Used when the number of interpolated values exceeds 8. + * + * ```html + *
+ * ``` + * + * Its compiled representation is: + * + * ```ts + * ɵɵstyleMapInterpolateV( + * ['key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5, + * '; key6: ', v6, '; key7: ', v7, '; key8: ', v8, '; key9: ', v9, 'suffix']); + * ``` + *. + * @param values The 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, '; key2: ', value1, '; key2: ', value2, ..., value99, 'suffix']`) + * @codeGenApi + */ +export function ɵɵstyleMapInterpolateV(values: any[]): void { + const lView = getLView(); + const interpolatedValue = interpolationV(lView, values); + ɵɵstyleMap(interpolatedValue); +} diff --git a/packages/core/src/render3/instructions/style_prop_interpolation.ts b/packages/core/src/render3/instructions/style_prop_interpolation.ts index a5d3ca7580..cde0b3335b 100644 --- a/packages/core/src/render3/instructions/style_prop_interpolation.ts +++ b/packages/core/src/render3/instructions/style_prop_interpolation.ts @@ -358,10 +358,10 @@ export function ɵɵstylePropInterpolate8( } /** - * Update an interpolated style property on an element with 8 or more bound values surrounded by + * Update an interpolated style property on an element with 9 or more bound values surrounded by * text. * - * Used when the number of interpolated values exceeds 7. + * Used when the number of interpolated values exceeds 8. * * ```html *
, key: string, value: any) { +export function styleKeyValueArraySet(keyValueArray: KeyValueArray, key: string, value: any) { if (stylePropNeedsSanitization(key)) { value = ɵɵsanitizeStyle(value); } diff --git a/packages/core/src/render3/instructions/text_interpolation.ts b/packages/core/src/render3/instructions/text_interpolation.ts index 801f1ae9b5..db25d4d52a 100644 --- a/packages/core/src/render3/instructions/text_interpolation.ts +++ b/packages/core/src/render3/instructions/text_interpolation.ts @@ -306,7 +306,7 @@ export function ɵɵtextInterpolate8( * 'suffix']); * ``` *. - * @param values The a collection of values and the strings in between those values, beginning with + * @param values The 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']`) * diff --git a/packages/core/src/render3/jit/environment.ts b/packages/core/src/render3/jit/environment.ts index 444869543a..bcc4172397 100644 --- a/packages/core/src/render3/jit/environment.ts +++ b/packages/core/src/render3/jit/environment.ts @@ -117,6 +117,15 @@ export const angularCoreEnv: {[name: string]: Function} = 'ɵɵclassMapInterpolate8': r3.ɵɵclassMapInterpolate8, 'ɵɵclassMapInterpolateV': r3.ɵɵclassMapInterpolateV, 'ɵɵstyleMap': r3.ɵɵstyleMap, + 'ɵɵstyleMapInterpolate1': r3.ɵɵstyleMapInterpolate1, + 'ɵɵstyleMapInterpolate2': r3.ɵɵstyleMapInterpolate2, + 'ɵɵstyleMapInterpolate3': r3.ɵɵstyleMapInterpolate3, + 'ɵɵstyleMapInterpolate4': r3.ɵɵstyleMapInterpolate4, + 'ɵɵstyleMapInterpolate5': r3.ɵɵstyleMapInterpolate5, + 'ɵɵstyleMapInterpolate6': r3.ɵɵstyleMapInterpolate6, + 'ɵɵstyleMapInterpolate7': r3.ɵɵstyleMapInterpolate7, + 'ɵɵstyleMapInterpolate8': r3.ɵɵstyleMapInterpolate8, + 'ɵɵstyleMapInterpolateV': r3.ɵɵstyleMapInterpolateV, 'ɵɵstyleProp': r3.ɵɵstyleProp, 'ɵɵstylePropInterpolate1': r3.ɵɵstylePropInterpolate1, 'ɵɵstylePropInterpolate2': r3.ɵɵstylePropInterpolate2, diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 9a9f1bef06..be35c567ef 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -757,15 +757,15 @@ describe('styling', () => { ` }) class Cmp { - one = 'one'; - two = 'two'; - three = 'three'; - four = 'four'; - five = 'five'; - six = 'six'; - seven = 'seven'; - eight = 'eight'; - nine = 'nine'; + one = '1'; + two = '2'; + three = '3'; + four = '4'; + five = '5'; + six = '6'; + seven = '7'; + eight = '8'; + nine = '9'; } TestBed.configureTestingModule({declarations: [Cmp]}); @@ -775,16 +775,16 @@ describe('styling', () => { 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'); + expect(divs[0].getAttribute('class')).toBe('a1b2c3d4e5f6g7h8i9j'); + expect(divs[1].getAttribute('class')).toBe('a1b2c3d4e5f6g7h8i'); + expect(divs[2].getAttribute('class')).toBe('a1b2c3d4e5f6g7h'); + expect(divs[3].getAttribute('class')).toBe('a1b2c3d4e5f6g'); + expect(divs[4].getAttribute('class')).toBe('a1b2c3d4e5f'); + expect(divs[5].getAttribute('class')).toBe('a1b2c3d4e'); + expect(divs[6].getAttribute('class')).toBe('a1b2c3d'); + expect(divs[7].getAttribute('class')).toBe('a1b2c'); + expect(divs[8].getAttribute('class')).toBe('a1b'); + expect(divs[9].getAttribute('class')).toBe('1'); instance.one = instance.two = instance.three = instance.four = instance.five = instance.six = instance.seven = instance.eight = instance.nine = ''; @@ -802,6 +802,69 @@ describe('styling', () => { expect(divs[9].getAttribute('class')).toBeFalsy(); }); + onlyInIvy('only Ivy supports style interpolation') + .it('should support interpolations inside a style binding', () => { + @Component({ + template: ` +
+
+
+
+
+
+
+
+
+
+ ` + }) + class Cmp { + self = 'content: "self"'; + 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 = fixture.nativeElement.querySelectorAll('div'); + + expect(divs[0].style.getPropertyValue('content')).toBe('"a1b2c3d4e5f6g7h8i9j"'); + expect(divs[1].style.getPropertyValue('content')).toBe('"a1b2c3d4e5f6g7h8i"'); + expect(divs[2].style.getPropertyValue('content')).toBe('"a1b2c3d4e5f6g7h"'); + expect(divs[3].style.getPropertyValue('content')).toBe('"a1b2c3d4e5f6g"'); + expect(divs[4].style.getPropertyValue('content')).toBe('"a1b2c3d4e5f"'); + expect(divs[5].style.getPropertyValue('content')).toBe('"a1b2c3d4e"'); + expect(divs[6].style.getPropertyValue('content')).toBe('"a1b2c3d"'); + expect(divs[7].style.getPropertyValue('content')).toBe('"a1b2c"'); + expect(divs[8].style.getPropertyValue('content')).toBe('"a1b"'); + expect(divs[9].style.getPropertyValue('content')).toBe('"self"'); + + instance.one = instance.two = instance.three = instance.four = instance.five = + instance.six = instance.seven = instance.eight = instance.nine = instance.self = ''; + fixture.detectChanges(); + + expect(divs[0].style.getPropertyValue('content')).toBe('"abcdefghij"'); + expect(divs[1].style.getPropertyValue('content')).toBe('"abcdefghi"'); + expect(divs[2].style.getPropertyValue('content')).toBe('"abcdefgh"'); + expect(divs[3].style.getPropertyValue('content')).toBe('"abcdefg"'); + expect(divs[4].style.getPropertyValue('content')).toBe('"abcdef"'); + expect(divs[5].style.getPropertyValue('content')).toBe('"abcde"'); + expect(divs[6].style.getPropertyValue('content')).toBe('"abcd"'); + expect(divs[7].style.getPropertyValue('content')).toBe('"abc"'); + expect(divs[8].style.getPropertyValue('content')).toBe('"ab"'); + expect(divs[9].style.getPropertyValue('content')).toBeFalsy(); + }); + it('should support interpolations inside a class binding when other classes are present', () => { @Component({template: '
'}) class Cmp { diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 9e60f3f254..2184ce1cf1 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -1037,6 +1037,24 @@ export declare function ɵɵstyleMap(styles: { [styleName: string]: any; } | string | undefined | null): void; +export declare function ɵɵstyleMapInterpolate1(prefix: string, v0: any, suffix: string): void; + +export declare function ɵɵstyleMapInterpolate2(prefix: string, v0: any, i0: string, v1: any, suffix: string): void; + +export declare function ɵɵstyleMapInterpolate3(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void; + +export declare function ɵɵstyleMapInterpolate4(prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, i2: string, v3: any, suffix: string): void; + +export declare function ɵɵstyleMapInterpolate5(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 ɵɵstyleMapInterpolate6(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 ɵɵstyleMapInterpolate7(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 ɵɵstyleMapInterpolate8(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 ɵɵstyleMapInterpolateV(values: any[]): void; + export declare function ɵɵstyleProp(prop: string, value: string | number | SafeValue | undefined | null, suffix?: string | null): typeof ɵɵstyleProp; export declare function ɵɵstylePropInterpolate1(prop: string, prefix: string, v0: any, suffix: string, valueSuffix?: string | null): typeof ɵɵstylePropInterpolate1;