fix(ivy): special case [style] and [class] bindings for future use (#23232)

PR Close #23232
This commit is contained in:
Matias Niemelä 2018-04-06 12:52:17 -07:00 committed by Miško Hevery
parent 8c1ac28275
commit 1b253e14ff
4 changed files with 122 additions and 4 deletions

View File

@ -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};

View File

@ -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<void>, LocalResolver {
private _dataIndex = 0;
private _bindingContext = 0;
@ -382,6 +390,18 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, 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?

View File

@ -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: \`<div [style]="myStyleExp"></div>\`
})
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: \`<div [class]="myClassExp"></div>\`
})
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');
});
});
});

View File

@ -142,11 +142,11 @@ The goal is for the `@Component` (and friends) to be the compiler of template. S
| `<div title="Hello {{name}}!">` | ✅ | ✅ | ✅ |
| `<div [attr.value]="exp">` | ✅ | ✅ | ❌ |
| `<div class="literal">` | ✅ | ✅ | ✅ |
| `<div [class]="exp">` | ❌ | ❌ | ❌ |
| `<div [class.foo]="exp">` | ✅ | ✅ | |
| `<div [class]="exp">` | ✅ | ✅ | ✅ |
| `<div [class.foo]="exp">` | ✅ | ✅ | |
| `<div style="literal">` | ✅ | ✅ | ✅ |
| `<div [style]="exp">` | ❌ | ❌ | ❌ |
| `<div [style.foo]="exp">` | ✅ | ✅ | |
| `<div [style]="exp">` | ✅ | ✅ | ✅ |
| `<div [style.foo]="exp">` | ✅ | ✅ | |
| `{{ ['literal', exp ] }}` | ✅ | ✅ | ✅ |
| `{{ { a: 'literal', b: exp } }}` | ✅ | ✅ | ✅ |
| `{{ exp \| pipe: arg }}` | ✅ | ✅ | ✅ |