diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index a6a68b5b76ff6..b4a4cdf37dedb 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -83,6 +83,11 @@ export enum OpKind { */ Property, + /** + * An operation to interpolate text into a property binding. + */ + InterpolateProperty, + /** * An operation to advance the runtime's implicit slot context during the update phase of a view. */ diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index 6fe8d3d35ba7e..1017e00fbae19 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -459,6 +459,11 @@ export function transformExpressionsInOp( case OpKind.Property: op.expression = transformExpressionsInExpression(op.expression, transform, flags); break; + case OpKind.InterpolateProperty: + for (let i = 0; i < op.expressions.length; i++) { + op.expressions[i] = transformExpressionsInExpression(op.expressions[i], transform, flags); + } + break; case OpKind.Statement: transformExpressionsInStatement(op.statement, transform, flags); break; diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts index 1619f6a636dfd..b5e84a8502575 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts @@ -16,8 +16,8 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared'; /** * An operation usable on the update side of the IR. */ -export type UpdateOp = ListEndOp|StatementOp|PropertyOp|InterpolateTextOp| - AdvanceOp|VariableOp; +export type UpdateOp = ListEndOp|StatementOp|PropertyOp|InterpolatePropertyOp| + InterpolateTextOp|AdvanceOp|VariableOp; /** * A logical operation to perform string interpolation on a text node. @@ -102,6 +102,60 @@ export function createPropertyOp(xref: XrefId, name: string, expression: o.Expre }; } + + +/** + * A logical operation representing binding an interpolation to a property in the update IR. + */ +export interface InterpolatePropertyOp extends Op, ConsumesVarsTrait, + DependsOnSlotContextOpTrait { + kind: OpKind.InterpolateProperty; + + /** + * Reference to the element on which the property is bound. + */ + target: XrefId; + + /** + * Name of the bound property. + */ + name: string; + + /** + * 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 `InterpolateProperty`. + */ +export function createInterpolatePropertyOp( + xref: XrefId, name: string, strings: string[], + expressions: o.Expression[]): InterpolatePropertyOp { + return { + kind: OpKind.InterpolateProperty, + target: xref, + name, + 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. */ diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 188a07914d3ee..118184de53204 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -214,19 +214,15 @@ function ingestAttributes(op: ir.ElementOpBase, element: t.Element|t.Template): function ingestBindings( view: ViewCompilation, op: ir.ElementOpBase, element: t.Element|t.Template): void { if (element instanceof t.Template) { - // TODO: Are ng-template inputs handled differently from element inputs? - // - // - for (const input of [...element.templateAttrs, ...element.inputs]) { - if (!(input instanceof t.BoundAttribute)) { + for (const attr of [...element.templateAttrs, ...element.inputs]) { + if (!(attr instanceof t.BoundAttribute)) { continue; } - - view.update.push(ir.createPropertyOp(op.xref, input.name, convertAst(input.value, view.tpl))); + ingestPropertyBinding(view, op.xref, attr.name, attr.value); } } else { for (const input of element.inputs) { - view.update.push(ir.createPropertyOp(op.xref, input.name, convertAst(input.value, view.tpl))); + ingestPropertyBinding(view, op.xref, input.name, input.value); } for (const output of element.outputs) { @@ -262,6 +258,19 @@ function ingestBindings( } } +function ingestPropertyBinding( + view: ViewCompilation, xref: ir.XrefId, name: string, value: e.AST): void { + if (value instanceof e.ASTWithSource) { + value = value.ast; + } + if (value instanceof e.Interpolation) { + view.update.push(ir.createInterpolatePropertyOp( + xref, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl)))); + } else { + view.update.push(ir.createPropertyOp(xref, name, convertAst(value, view.tpl))); + } +} + /** * Process all of the local references on an element-like structure in the template AST and convert * them to their IR representation. diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index 9304895c2371d..a2215f3bdf0d0 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -189,6 +189,29 @@ export function textInterpolate(strings: string[], expressions: o.Expression[]): return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs); } + +export function propertyInterpolate( + name: string, strings: string[], expressions: o.Expression[]): ir.UpdateOp { + if (strings.length < 1 || expressions.length !== strings.length - 1) { + throw new Error( + `AssertionError: expected specific shape of args for strings/expressions in interpolation`); + } + const interpolationArgs: o.Expression[] = []; + + if (expressions.length === 1 && strings[0] === '' && strings[1] === '') { + interpolationArgs.push(expressions[0]); + } else { + let idx: number; + for (idx = 0; idx < expressions.length; idx++) { + interpolationArgs.push(o.literal(strings[idx]), expressions[idx]); + } + // idx points at the last string. + interpolationArgs.push(o.literal(strings[idx])); + } + + return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [o.literal(name)], interpolationArgs); +} + export function pureFunction( varOffset: number, fn: o.Expression, args: o.Expression[]): o.Expression { return callVariadicInstructionExpr( @@ -240,6 +263,31 @@ const TEXT_INTERPOLATE_CONFIG: VariadicInstructionConfig = { }, }; + +/** + * `InterpolationConfig` for the `propertyInterpolate` instruction. + */ +const PROPERTY_INTERPOLATE_CONFIG: VariadicInstructionConfig = { + constant: [ + Identifiers.propertyInterpolate, + Identifiers.propertyInterpolate1, + Identifiers.propertyInterpolate2, + Identifiers.propertyInterpolate3, + Identifiers.propertyInterpolate4, + Identifiers.propertyInterpolate5, + Identifiers.propertyInterpolate6, + Identifiers.propertyInterpolate7, + Identifiers.propertyInterpolate8, + ], + variable: Identifiers.propertyInterpolateV, + mapping: n => { + if (n % 2 === 0) { + throw new Error(`Expected odd number of arguments`); + } + return (n - 1) / 2; + }, +}; + const PURE_FUNCTION_CONFIG: VariadicInstructionConfig = { constant: [ Identifiers.pureFunction0, diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index fd4f9ec2cfdaf..9c3d6cb012656 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -121,6 +121,9 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList