Skip to content

Commit

Permalink
refactor(compiler): add support for class map interpolation bindings (a…
Browse files Browse the repository at this point in the history
…ngular#50805)

Adds support for bindings of the form `class="static {{dynamic}}"`

PR Close angular#50805
  • Loading branch information
mmalerba authored and thePunderWoman committed Jul 17, 2023
1 parent 7e5e37a commit 0210259
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
"failureMessage": "Incorrect template",
"files": ["binding_slots.js"]
}
],
"skipForTemplatePipeline": true
]
},
{
"description": "should place initial, multi, singular and application followed by attribute style instructions in the template code in that order",
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export enum OpKind {
*/
InterpolateStyleMap,

/**
* An operation to interpolate text into a class mapping.
*/
InterpolateClassMap,

/**
* An operation to advance the runtime's implicit slot context during the update phase of a view.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ export function transformExpressionsInOp(
case OpKind.InterpolateProperty:
case OpKind.InterpolateStyleProp:
case OpKind.InterpolateStyleMap:
case OpKind.InterpolateClassMap:
case OpKind.InterpolateText:
for (let i = 0; i < op.expressions.length; i++) {
op.expressions[i] = transformExpressionsInExpression(op.expressions[i], transform, flags);
Expand Down
44 changes: 43 additions & 1 deletion packages/compiler/src/template/pipeline/ir/src/ops/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
*/
export type UpdateOp = ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|AttributeOp|StylePropOp|
StyleMapOp|ClassMapOp|InterpolatePropertyOp|InterpolateAttributeOp|InterpolateStylePropOp|
InterpolateStyleMapOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>;
InterpolateStyleMapOp|InterpolateClassMapOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>;

/**
* A logical operation to perform string interpolation on a text node.
Expand Down Expand Up @@ -463,6 +463,48 @@ export function createInterpolateStyleMapOp(
};
}

/**
* A logical operation representing binding an interpolation to a class mapping in the update IR.
*/
export interface InterpolateClassMapOp extends Op<UpdateOp>, ConsumesVarsTrait,
DependsOnSlotContextOpTrait {
kind: OpKind.InterpolateClassMap;

/**
* Reference to the element on which the property is bound.
*/
target: XrefId;

/**
* All of the literal strings in the property interpolation, in order.
*
* Conceptually interwoven around the `expressions`.
*/
strings: string[];

/**
* All of the dynamic expressions in the property interpolation, in order.
*
* Conceptually interwoven in between the `strings`.
*/
expressions: o.Expression[];
}

/**
* Create a `InterpolateStyleMap`.
*/
export function createInterpolateClassMapOp(
xref: XrefId, strings: string[], expressions: o.Expression[]): InterpolateClassMapOp {
return {
kind: OpKind.InterpolateClassMap,
target: xref,
strings,
expressions,
...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
...TRAIT_CONSUMES_VARS,
...NEW_OP,
};
}

/**
* Logical operation to advance the runtime's internal slot pointer in the update IR.
Expand Down
6 changes: 6 additions & 0 deletions packages/compiler/src/template/pipeline/src/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ function ingestPropertyBinding(
}
view.update.push(ir.createInterpolateStyleMapOp(
xref, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
} else if (name === 'class') {
if (bindingKind !== ir.ElementAttributeKind.Binding) {
throw Error('Unexpected class binding on ng-template');
}
view.update.push(ir.createInterpolateClassMapOp(
xref, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
} else {
view.update.push(ir.createInterpolatePropertyOp(
xref, bindingKind, name, value.strings,
Expand Down
33 changes: 33 additions & 0 deletions packages/compiler/src/template/pipeline/src/instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ export function styleMapInterpolate(strings: string[], expressions: o.Expression
return callVariadicInstruction(STYLE_MAP_INTERPOLATE_CONFIG, [], interpolationArgs);
}

export function classMapInterpolate(strings: string[], expressions: o.Expression[]): ir.UpdateOp {
const interpolationArgs = collateInterpolationArgs(strings, expressions);

return callVariadicInstruction(CLASS_MAP_INTERPOLATE_CONFIG, [], interpolationArgs);
}

export function pureFunction(
varOffset: number, fn: o.Expression, args: o.Expression[]): o.Expression {
return callVariadicInstructionExpr(
Expand Down Expand Up @@ -421,6 +427,33 @@ const STYLE_MAP_INTERPOLATE_CONFIG: VariadicInstructionConfig = {
},
};

/**
* `InterpolationConfig` for the `classMapInterpolate` instruction.
*/
const CLASS_MAP_INTERPOLATE_CONFIG: VariadicInstructionConfig = {
constant: [
null!, // Single argument classMapInterpolate is converted to classMap instruction.
Identifiers.classMapInterpolate1,
Identifiers.classMapInterpolate2,
Identifiers.classMapInterpolate3,
Identifiers.classMapInterpolate4,
Identifiers.classMapInterpolate5,
Identifiers.classMapInterpolate6,
Identifiers.classMapInterpolate7,
Identifiers.classMapInterpolate8,
],
variable: Identifiers.classMapInterpolateV,
mapping: n => {
if (n % 2 === 0) {
throw new Error(`Expected odd number of arguments`);
}
if (n < 3) {
throw new Error(`Expected at least 3 arguments`);
}
return (n - 1) / 2;
},
};

const PURE_FUNCTION_CONFIG: VariadicInstructionConfig = {
constant: [
Identifiers.pureFunction0,
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/src/template/pipeline/src/phases/reify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList<ir.UpdateO
case ir.OpKind.InterpolateStyleMap:
ir.OpList.replace(op, ng.styleMapInterpolate(op.strings, op.expressions));
break;
case ir.OpKind.InterpolateClassMap:
ir.OpList.replace(op, ng.classMapInterpolate(op.strings, op.expressions));
break;
case ir.OpKind.InterpolateText:
ir.OpList.replace(op, ng.textInterpolate(op.strings, op.expressions));
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ function varsUsedByOp(op: (ir.CreateOp|ir.UpdateOp)&ir.ConsumesVarsTrait): numbe
case ir.OpKind.InterpolateAttribute:
// One variable slot for each dynamic expression, plus one for the result.
return 1 + op.expressions.length;
case ir.OpKind.InterpolateClassMap:
// TODO: explain why 2+n.
return 2 + op.expressions.length;
default:
throw new Error(`Unhandled op: ${ir.OpKind[op.kind]}`);
}
Expand Down

0 comments on commit 0210259

Please sign in to comment.