fix(ivy): special case [style] and [class] bindings for future use (#23232)
PR Close #23232
This commit is contained in:
parent
8c1ac28275
commit
1b253e14ff
|
@ -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};
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 }}` | ✅ | ✅ | ✅ |
|
||||
|
|
Loading…
Reference in New Issue