diff --git a/packages/compiler/src/render3/view/styling_builder.ts b/packages/compiler/src/render3/view/styling_builder.ts index 46252453c8ad8..2ac2d6114ee93 100644 --- a/packages/compiler/src/render3/view/styling_builder.ts +++ b/packages/compiler/src/render3/view/styling_builder.ts @@ -202,8 +202,7 @@ export class StylingBuilder { let binding: BoundStylingEntry|null = null; const prefix = name.substring(0, 6); const isStyle = name === 'style' || prefix === 'style.' || prefix === 'style!'; - const isClass = !isStyle && - (name === 'class' || name === 'className' || prefix === 'class.' || prefix === 'class!'); + const isClass = !isStyle && (name === 'class' || prefix === 'class.' || prefix === 'class!'); if (isStyle || isClass) { const isMapBased = name.charAt(5) !== '.'; // style.prop or class.prop makes this a no const property = name.substr(isMapBased ? 5 : 6); // the dot explains why there's a +1 diff --git a/packages/core/src/render3/instructions/property.ts b/packages/core/src/render3/instructions/property.ts index dda96ec0534f1..e02a8808a4033 100644 --- a/packages/core/src/render3/instructions/property.ts +++ b/packages/core/src/render3/instructions/property.ts @@ -53,6 +53,5 @@ export function setDirectiveInputsWhichShadowsStyling( const inputs = tNode.inputs !; const property = isClassBased ? 'class' : 'style'; // We support both 'class' and `className` hence the fallback. - const stylingInputs = inputs[property] || (isClassBased && inputs['className']); - setInputsForProperty(tView, lView, stylingInputs, property, value); + setInputsForProperty(tView, lView, inputs[property], property, value); } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index ff00a5679cc4e..8c0fa14d48bc0 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -907,7 +907,7 @@ function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void { } if (inputsStore !== null) { - if (inputsStore.hasOwnProperty('class') || inputsStore.hasOwnProperty('className')) { + if (inputsStore.hasOwnProperty('class')) { tNode.flags |= TNodeFlags.hasClassInput; } if (inputsStore.hasOwnProperty('style')) { diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index 63278e8bcf399..02397afb41e43 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -1129,17 +1129,17 @@ describe('styling', () => { }); onlyInIvy('only ivy combines static and dynamic class-related attr values') - .it('should write to a `className` input binding, when static `class` is present', () => { + .it('should write combined class attribute and class binding to the class input', () => { @Component({ selector: 'comp', template: `{{className}}`, }) class Comp { - @Input() className: string = ''; + @Input('class') className: string = ''; } @Component({ - template: ``, + template: ``, }) class App { } @@ -1150,32 +1150,6 @@ describe('styling', () => { expect(fixture.debugElement.nativeElement.firstChild.innerHTML).toBe('static my-className'); }); - onlyInIvy('in Ivy [class] and [className] bindings on the same element are not allowed') - .it('should throw an error in case [class] and [className] bindings are used on the same element', - () => { - @Component({ - selector: 'comp', - template: `{{class}} - {{className}}`, - }) - class Comp { - @Input() class: string = ''; - @Input() className: string = ''; - } - @Component({ - template: ``, - }) - class App { - } - - TestBed.configureTestingModule({declarations: [Comp, App]}); - expect(() => { - const fixture = TestBed.createComponent(App); - fixture.detectChanges(); - }) - .toThrowError( - '[class] and [className] bindings cannot be used on the same element simultaneously'); - }); - onlyInIvy('only ivy persists static class/style attrs with their binding counterparts') .it('should write to a `class` input binding if there is a static class value and there is a binding value', () => { @@ -3460,13 +3434,12 @@ describe('styling', () => { class MyApp { // When the first view in the list gets CD-ed, everything works. // When the second view gets CD-ed, the styling has already created the data structures - // in the `TView`. As a result when - // `[class.foo]` runs it already knows that `[class]` is a duplicate and hence it - // should check with it. While the resolution is happening it reads the value of the - // `[class]`, however `[class]` has not yet executed and therefore it does not have - // normalized value in its `LView`. The result is that the assertions fails as it - // expects an - // `KeyValueArray`. + // in the `TView`. As a result when `[class.foo]` runs it already knows that `[class]` + // is a duplicate and hence it can overwrite the `[class.foo]` binding. While the + // styling resolution is happening the algorithm reads the value of the `[class]` + // (because it overwrites `[class.foo]`), however `[class]` has not yet executed and + // therefore it does not have normalized value in its `LView`. The result is that the + // assertions fails as it expects an `KeyValueArray`. } TestBed.configureTestingModule({declarations: [MyApp, MyCmp, HostStylingsDir]}); @@ -3476,6 +3449,44 @@ describe('styling', () => { expectClass(cmp1).toEqual({foo: true, bar: true}); expectClass(cmp2).toEqual({foo: true, bar: true}); }); + + it('should not bind [class] to @Input("className")', () => { + @Component({ + selector: 'my-cmp', + template: `className = {{className}}`, + }) + class MyCmp { + @Input() + className: string = 'unbound'; + } + @Component({template: ``}) + class MyApp { + } + + TestBed.configureTestingModule({declarations: [MyApp, MyCmp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toEqual('className = unbound'); + }); + + it('should not bind class to @Input("className")', () => { + @Component({ + selector: 'my-cmp', + template: `className = {{className}}`, + }) + class MyCmp { + @Input() + className: string = 'unbound'; + } + @Component({template: ``}) + class MyApp { + } + + TestBed.configureTestingModule({declarations: [MyApp, MyCmp]}); + const fixture = TestBed.createComponent(MyApp); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent).toEqual('className = unbound'); + }); }); });