diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 6cdcdc8404..e5fb36727e 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -31,8 +31,12 @@ export class Identifiers { static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE}; + static elementClass: o.ExternalReference = {name: 'ɵk', moduleName: CORE}; + static elementClassNamed: o.ExternalReference = {name: 'ɵkn', moduleName: CORE}; + static elementStyle: o.ExternalReference = {name: 'ɵs', moduleName: CORE}; + static elementStyleNamed: o.ExternalReference = {name: 'ɵsn', moduleName: CORE}; static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index a9f535f848..eca3a45a39 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -39,6 +39,14 @@ const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = { [BindingType.Style]: R3.elementStyleNamed, }; +// `className` is used below instead of `class` because the interception +// code (where this map is used) deals with DOM element property values +// (like elm.propName) and not component bindining properties (like [propName]). +const SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP: {[index: string]: o.ExternalReference} = { + 'className': R3.elementClass, + 'style': R3.elementStyle +}; + export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver { private _dataIndex = 0; private _bindingContext = 0; @@ -382,6 +390,18 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this._unsupported('animations'); } const convertedBinding = this.convertPropertyBinding(implicit, input.value); + const specialInstruction = SPECIAL_CASED_PROPERTIES_INSTRUCTION_MAP[input.name]; + if (specialInstruction) { + // special case for [style] and [class] bindings since they are not handled as + // standard properties within this implementation. Instead they are + // handed off to special cased instruction handlers which will then + // delegate them as animation sequences (or input bindings for dirs/cmps) + this.instruction( + this._bindingCode, input.sourceSpan, specialInstruction, o.literal(elementIndex), + convertedBinding); + return; + } + const instruction = BINDING_INSTRUCTION_MAP[input.type]; if (instruction) { // TODO(chuckj): runtime: security context? diff --git a/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts b/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts new file mode 100644 index 0000000000..df527a99d5 --- /dev/null +++ b/packages/compiler/test/render3/r3_view_compiler_styling_spec.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {MockDirectory, setup} from '../aot/test_util'; +import {compile, expectEmit} from './mock_compile'; + +describe('compiler compliance: styling', () => { + const angularFiles = setup({ + compileAngular: true, + compileAnimations: false, + compileCommon: true, + }); + + describe('[style]', () => { + it('should create style instructions on the element', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \`
\` + }) + export class MyComponent { + myStyleExp = [{color:'red'}, {color:'blue', duration:1000}] + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { + if (rf & 1) { + $r3$.ɵE(0, 'div'); + $r3$.ɵe(); + } + if (rf & 2) { + $r3$.ɵs(0,$r3$.ɵb($ctx$.myStyleExp)); + } + } + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + }); + + describe('[class]', () => { + it('should create class styling instructions on the element', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-component', + template: \`
\` + }) + export class MyComponent { + myClassExp = [{color:'orange'}, {color:'green', duration:1000}] + } + + @NgModule({declarations: [MyComponent]}) + export class MyModule {} + ` + } + }; + + const template = ` + template: function MyComponent_Template(rf: $RenderFlags$, $ctx$: $MyComponent$) { + if (rf & 1) { + $r3$.ɵE(0, 'div'); + $r3$.ɵe(); + } + if (rf & 2) { + $r3$.ɵk(0,$r3$.ɵb($ctx$.myClassExp)); + } + } + `; + + const result = compile(files, angularFiles); + expectEmit(result.source, template, 'Incorrect template'); + }); + }); +}); diff --git a/packages/core/src/render3/STATUS.md b/packages/core/src/render3/STATUS.md index c0a0daae8a..a8de96258b 100644 --- a/packages/core/src/render3/STATUS.md +++ b/packages/core/src/render3/STATUS.md @@ -142,11 +142,11 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S | `
` | ✅ | ✅ | ✅ | | `
` | ✅ | ✅ | ❌ | | `
` | ✅ | ✅ | ✅ | -| `
` | ❌ | ❌ | ❌ | -| `
` | ✅ | ✅ | ❌ | +| `
` | ✅ | ✅ | ✅ | +| `
` | ✅ | ✅ | ✅ | | `
` | ✅ | ✅ | ✅ | -| `
` | ❌ | ❌ | ❌ | -| `
` | ✅ | ✅ | ❌ | +| `
` | ✅ | ✅ | ✅ | +| `
` | ✅ | ✅ | ✅ | | `{{ ['literal', exp ] }}` | ✅ | ✅ | ✅ | | `{{ { a: 'literal', b: exp } }}` | ✅ | ✅ | ✅ | | `{{ exp \| pipe: arg }}` | ✅ | ✅ | ✅ |