Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ivy): Add style="{{exp}}" based interpolation #34202

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
180 changes: 151 additions & 29 deletions packages/compiler-cli/test/compliance/r3_view_compiler_styling_spec.ts
Expand Up @@ -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);
}
}
`;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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: \`
<div class="A{{p1}}B"></div>
<div class="A{{p1}}B{{p2}}C"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E{{p5}}F"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E{{p5}}F{{p6}}G"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E{{p5}}F{{p6}}G{{p7}}H"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E{{p5}}F{{p6}}G{{p7}}H{{p8}}I"></div>
<div class="A{{p1}}B{{p2}}C{{p3}}D{{p4}}E{{p5}}F{{p6}}G{{p7}}H{{p8}}I{{p9}}J"></div>
\`,
})
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: \`
<div style="p1:{{p1}};"></div>
mhevery marked this conversation as resolved.
Show resolved Hide resolved
<div style="p1:{{p1}};p2:{{p2}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};p5:{{p5}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};p5:{{p5}};p6:{{p6}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};p5:{{p5}};p6:{{p6}};p7:{{p7}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};p5:{{p5}};p6:{{p6}};p7:{{p7}};p8:{{p8}};"></div>
<div style="p1:{{p1}};p2:{{p2}};p3:{{p3}};p4:{{p4}};p5:{{p5}};p6:{{p6}};p7:{{p7}};p8:{{p8}};p9:{{p9}};"></div>
\`,
})
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 = {
Expand Down Expand Up @@ -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: '<div style="color:{{red}}"></div>'
})
export class MyComponent {
}
`
}
};

expect(() => compile(files, angularFiles)).toThrowError(/Unexpected interpolation/);
});

it('should throw for interpolations inside individual class bindings', () => {
const files = {
app: {
Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
19 changes: 19 additions & 0 deletions packages/compiler/src/render3/r3_identifiers.ts
Expand Up @@ -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:
Expand Down
44 changes: 34 additions & 10 deletions packages/compiler/src/render3/view/styling_builder.ts
Expand Up @@ -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[]);
}
Expand Down Expand Up @@ -372,28 +372,23 @@ 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;
}

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;
}
}]
Expand Down Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/core_render3_private_export.ts
Expand Up @@ -121,6 +121,15 @@ export {
ɵɵelementContainerEnd,
ɵɵelementContainer,
ɵɵstyleMap,
ɵɵstyleMapInterpolate1,
ɵɵstyleMapInterpolate2,
ɵɵstyleMapInterpolate3,
ɵɵstyleMapInterpolate4,
ɵɵstyleMapInterpolate5,
ɵɵstyleMapInterpolate6,
ɵɵstyleMapInterpolate7,
ɵɵstyleMapInterpolate8,
ɵɵstyleMapInterpolateV,
ɵɵstyleSanitizer,
ɵɵclassMap,
ɵɵclassMapInterpolate1,
Expand Down