Skip to content

Commit 7e5e37a

Browse files
mmalerbathePunderWoman
authored andcommitted
refactor(compiler): add support for class map bindings (angular#50805)
Adds support for bindings of the form `[class]="classActiveMap"` PR Close angular#50805
1 parent c5608e5 commit 7e5e37a

File tree

8 files changed

+63
-28
lines changed

8 files changed

+63
-28
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/TEST_CASES.json

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,43 @@
33
"cases": [
44
{
55
"description": "should create class styling instructions on the element",
6-
"inputFiles": [
7-
"class_binding.ts"
8-
],
6+
"inputFiles": ["class_binding.ts"],
97
"expectations": [
108
{
119
"failureMessage": "Incorrect template",
12-
"files": [
13-
"class_binding.js"
14-
]
10+
"files": ["class_binding.js"]
1511
}
16-
],
17-
"skipForTemplatePipeline": true
12+
]
1813
},
1914
{
2015
"description": "should place initial, multi, singular and application followed by attribute class instructions in the template code in that order",
21-
"inputFiles": [
22-
"class_ordering.ts"
23-
],
16+
"inputFiles": ["class_ordering.ts"],
2417
"expectations": [
2518
{
2619
"failureMessage": "Incorrect template",
27-
"files": [
28-
"class_ordering.js"
29-
]
20+
"files": ["class_ordering.js"]
3021
}
3122
],
3223
"skipForTemplatePipeline": true
3324
},
3425
{
3526
"description": "should not generate the styling apply instruction if there are only static style/class attributes",
36-
"inputFiles": [
37-
"static_bindings.ts"
38-
],
27+
"inputFiles": ["static_bindings.ts"],
3928
"expectations": [
4029
{
4130
"failureMessage": "Incorrect template",
42-
"files": [
43-
"static_bindings.js"
44-
]
31+
"files": ["static_bindings.js"]
4532
}
4633
],
4734
"skipForTemplatePipeline": true
4835
},
4936
{
5037
"description": "should not create instructions for empty class bindings",
51-
"inputFiles": [
52-
"empty_class_bindings.ts"
53-
],
38+
"inputFiles": ["empty_class_bindings.ts"],
5439
"expectations": [
5540
{
5641
"failureMessage": "Incorrect template",
57-
"files": [
58-
"empty_class_bindings.js"
59-
]
42+
"files": ["empty_class_bindings.js"]
6043
}
6144
],
6245
"skipForTemplatePipeline": true

packages/compiler/src/template/pipeline/ir/src/enums.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ export enum OpKind {
9393
*/
9494
StyleMap,
9595

96+
/**
97+
* An operation to bind an expression to the classes of an element.
98+
*/
99+
ClassMap,
100+
96101
/**
97102
* An operation to interpolate text into a property binding.
98103
*/

packages/compiler/src/template/pipeline/ir/src/expression.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ export function transformExpressionsInOp(
737737
case OpKind.Property:
738738
case OpKind.StyleProp:
739739
case OpKind.StyleMap:
740+
case OpKind.ClassMap:
740741
op.expression = transformExpressionsInExpression(op.expression, transform, flags);
741742
break;
742743
case OpKind.InterpolateProperty:

packages/compiler/src/template/pipeline/ir/src/ops/update.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
1818
* An operation usable on the update side of the IR.
1919
*/
2020
export type UpdateOp = ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|AttributeOp|StylePropOp|
21-
StyleMapOp|InterpolatePropertyOp|InterpolateAttributeOp|InterpolateStylePropOp|
22-
InterpolateStyleMapOp|AttributeOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>;
21+
StyleMapOp|ClassMapOp|InterpolatePropertyOp|InterpolateAttributeOp|InterpolateStylePropOp|
22+
InterpolateStyleMapOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>;
2323

2424
/**
2525
* A logical operation to perform string interpolation on a text node.
@@ -183,6 +183,37 @@ export function createStyleMapOp(xref: XrefId, expression: o.Expression): StyleM
183183
};
184184
}
185185

186+
/**
187+
* A logical operation representing binding to a style map in the update IR.
188+
*/
189+
export interface ClassMapOp extends Op<UpdateOp>, ConsumesVarsTrait, DependsOnSlotContextOpTrait {
190+
kind: OpKind.ClassMap;
191+
192+
/**
193+
* Reference to the element on which the property is bound.
194+
*/
195+
target: XrefId;
196+
197+
/**
198+
* Expression which is bound to the property.
199+
*/
200+
expression: o.Expression;
201+
}
202+
203+
/**
204+
* Create a `ClassMapOp`.
205+
*/
206+
export function createClassMapOp(xref: XrefId, expression: o.Expression): ClassMapOp {
207+
return {
208+
kind: OpKind.ClassMap,
209+
target: xref,
210+
expression,
211+
...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
212+
...TRAIT_CONSUMES_VARS,
213+
...NEW_OP,
214+
};
215+
}
216+
186217
/**
187218
* A logical operation representing setting an attribute on an element in the update IR.
188219
*/

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,11 @@ function ingestPropertyBinding(
307307
throw Error('Unexpected style binding on ng-template');
308308
}
309309
view.update.push(ir.createStyleMapOp(xref, convertAst(value, view.tpl)));
310+
} else if (name === 'class') {
311+
if (bindingKind !== ir.ElementAttributeKind.Binding) {
312+
throw Error('Unexpected class binding on ng-template');
313+
}
314+
view.update.push(ir.createClassMapOp(xref, convertAst(value, view.tpl)));
310315
} else {
311316
view.update.push(
312317
ir.createPropertyOp(xref, bindingKind, name, convertAst(value, view.tpl)));

packages/compiler/src/template/pipeline/src/instruction.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ export function styleMap(expression: o.Expression): ir.UpdateOp {
156156
return call(Identifiers.styleMap, [expression]);
157157
}
158158

159+
export function classMap(expression: o.Expression): ir.UpdateOp {
160+
return call(Identifiers.classMap, [expression]);
161+
}
162+
159163
const PIPE_BINDINGS: o.ExternalReference[] = [
160164
Identifiers.pipeBind1,
161165
Identifiers.pipeBind2,

packages/compiler/src/template/pipeline/src/phases/reify.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList<ir.UpdateO
127127
case ir.OpKind.StyleMap:
128128
ir.OpList.replace(op, ng.styleMap(op.expression));
129129
break;
130+
case ir.OpKind.ClassMap:
131+
ir.OpList.replace(op, ng.classMap(op.expression));
132+
break;
130133
case ir.OpKind.InterpolateProperty:
131134
ir.OpList.replace(op, ng.propertyInterpolate(op.name, op.strings, op.expressions));
132135
break;

packages/compiler/src/template/pipeline/src/phases/var_counting.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ function varsUsedByOp(op: (ir.CreateOp|ir.UpdateOp)&ir.ConsumesVarsTrait): numbe
6969
case ir.OpKind.Attribute:
7070
// Attribute bindings use 1 variable slot.
7171
return 1;
72+
case ir.OpKind.ClassMap:
73+
// TODO: explain why 2.
74+
return 2;
7275
case ir.OpKind.InterpolateText:
7376
// `ir.InterpolateTextOp`s use a variable slot for each dynamic expression.
7477
return op.expressions.length;

0 commit comments

Comments
 (0)