diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js index 0bfc4eeebb2..8a7bb32639a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js @@ -158,10 +158,10 @@ import * as i0 from "@angular/core"; export class MyComponent { } MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
`, isInline: true }); +MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
`, isInline: true }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ type: Component, - args: [{ selector: 'my-component', template: `
` }] + args: [{ selector: 'my-component', template: `
` }] }] }); export class MyModule { } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/TEST_CASES.json index a9f4a29cf21..295713f5006 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/TEST_CASES.json @@ -41,8 +41,7 @@ "failureMessage": "Incorrect template", "files": ["empty_class_bindings.js"] } - ], - "skipForTemplatePipeline": true + ] } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/empty_class_bindings.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/empty_class_bindings.ts index bf2ca528f0e..5998475ff06 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/empty_class_bindings.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/empty_class_bindings.ts @@ -1,6 +1,6 @@ import {Component, NgModule} from '@angular/core'; -@Component({selector: 'my-component', template: `
`}) +@Component({selector: 'my-component', template: `
`}) export class MyComponent { } diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index 9d7317aee3c..192bc2873e1 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -88,6 +88,11 @@ export enum OpKind { */ StyleProp, + /** + * An operation to bind an expression to a class property of an element. + */ + ClassProp, + /** * An operation to bind an expression to the styles of an element. */ diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index a72952cebac..83c47486087 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -737,6 +737,7 @@ export function transformExpressionsInOp( case OpKind.Property: case OpKind.StyleProp: case OpKind.StyleMap: + case OpKind.ClassProp: case OpKind.ClassMap: op.expression = transformExpressionsInExpression(op.expression, 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 8f4b642a39d..c2c7db08922 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/update.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/update.ts @@ -17,7 +17,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|AttributeOp|StylePropOp| +export type UpdateOp = + ListEndOp|StatementOp|PropertyOp|AttributeOp|StylePropOp|ClassPropOp| StyleMapOp|ClassMapOp|InterpolatePropertyOp|InterpolateAttributeOp|InterpolateStylePropOp| InterpolateStyleMapOp|InterpolateClassMapOp|InterpolateTextOp|AdvanceOp|VariableOp; @@ -154,6 +155,44 @@ export function createStylePropOp( }; } +/** + * A logical operation representing binding to a class property in the update IR. + */ +export interface ClassPropOp extends Op, ConsumesVarsTrait, DependsOnSlotContextOpTrait { + kind: OpKind.ClassProp; + + /** + * Reference to the element on which the property is bound. + */ + target: XrefId; + + /** + * Name of the bound property. + */ + name: string; + + /** + * Expression which is bound to the property. + */ + expression: o.Expression; +} + +/** + * Create a `ClassPropOp`. + */ +export function createClassPropOp( + xref: XrefId, name: string, expression: o.Expression): ClassPropOp { + return { + kind: OpKind.ClassProp, + target: xref, + name, + expression, + ...TRAIT_DEPENDS_ON_SLOT_CONTEXT, + ...TRAIT_CONSUMES_VARS, + ...NEW_OP, + }; +} + /** * A logical operation representing binding to a style map 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 8bf27914148..5f9af62108f 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -300,8 +300,11 @@ function ingestPropertyBinding( value.expressions.map(expr => convertAst(expr, view.tpl))); view.update.push(attributeInterpolate); break; + case e.BindingType.Class: + throw Error('Unexpected interpolation in class property binding'); + // TODO: implement remaining binding types. + case e.BindingType.Animation: default: - // TODO: implement remaining binding types. throw Error(`Interpolated property binding type not handled: ${type}`); } } else { @@ -336,8 +339,15 @@ function ingestPropertyBinding( const attrOp = ir.createAttributeOp(xref, bindingKind, name, convertAst(value, view.tpl)); view.update.push(attrOp); break; + case e.BindingType.Class: + if (bindingKind !== ir.ElementAttributeKind.Binding) { + throw Error('Unexpected class binding on ng-template'); + } + view.update.push(ir.createClassPropOp(xref, name, convertAst(value, view.tpl))); + break; + // TODO: implement remaining binding types. + case e.BindingType.Animation: default: - // TODO: implement remaining binding types. throw Error(`Property binding type not handled: ${type}`); } } diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index 9f24f8e6178..620c95a9c28 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -152,6 +152,10 @@ export function styleProp(name: string, expression: o.Expression, unit: string|n return call(Identifiers.styleProp, args); } +export function classProp(name: string, expression: o.Expression): ir.UpdateOp { + return call(Identifiers.classProp, [o.literal(name), expression]); +} + export function styleMap(expression: o.Expression): ir.UpdateOp { return call(Identifiers.styleMap, [expression]); } diff --git a/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts b/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts index dc2be738461..a1c326feb92 100644 --- a/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts +++ b/packages/compiler/src/template/pipeline/src/phases/attribute_extraction.ts @@ -90,6 +90,7 @@ function populateElementAttributes(view: ViewCompilation, compatibility: boolean break; case ir.OpKind.StyleProp: + case ir.OpKind.ClassProp: ownerOp = lookupElement(elements, op.target); ir.assertIsElementAttributes(ownerOp.attributes); diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index 8b1cd504d42..f11fcf87b24 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -124,6 +124,9 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList