Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a153b61

Browse files
mheveryatscott
authored andcommitted
fix(core): treat [class] and [className] as unrelated bindings (#35668)
Before this change `[class]` and `[className]` were both converted into `ɵɵclassMap`. The implication of this is that at runtime we could not differentiate between the two and as a result we treated `@Input('class')` and `@Input('className)` as equivalent. This change makes `[class]` and `[className]` distinct. The implication of this is that `[class]` becomes `ɵɵclassMap` instruction but `[className]` becomes `ɵɵproperty' instruction. This means that `[className]` will no longer participate in styling and will overwrite the DOM `class` value. Fix #35577 PR Close #35668
1 parent a4e956a commit a153b61

File tree

4 files changed

+44
-34
lines changed

4 files changed

+44
-34
lines changed

‎packages/compiler/src/render3/view/styling_builder.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,7 @@ export class StylingBuilder {
202202
let binding: BoundStylingEntry|null = null;
203203
const prefix = name.substring(0, 6);
204204
const isStyle = name === 'style' || prefix === 'style.' || prefix === 'style!';
205-
const isClass = !isStyle &&
206-
(name === 'class' || name === 'className' || prefix === 'class.' || prefix === 'class!');
205+
const isClass = !isStyle && (name === 'class' || prefix === 'class.' || prefix === 'class!');
207206
if (isStyle || isClass) {
208207
const isMapBased = name.charAt(5) !== '.'; // style.prop or class.prop makes this a no
209208
const property = name.substr(isMapBased ? 5 : 6); // the dot explains why there's a +1

‎packages/core/src/render3/instructions/property.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,5 @@ export function setDirectiveInputsWhichShadowsStyling(
5353
const inputs = tNode.inputs !;
5454
const property = isClassBased ? 'class' : 'style';
5555
// We support both 'class' and `className` hence the fallback.
56-
const stylingInputs = inputs[property] || (isClassBased && inputs['className']);
57-
setInputsForProperty(tView, lView, stylingInputs, property, value);
56+
setInputsForProperty(tView, lView, inputs[property], property, value);
5857
}

‎packages/core/src/render3/instructions/shared.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ function initializeInputAndOutputAliases(tView: TView, tNode: TNode): void {
907907
}
908908

909909
if (inputsStore !== null) {
910-
if (inputsStore.hasOwnProperty('class') || inputsStore.hasOwnProperty('className')) {
910+
if (inputsStore.hasOwnProperty('class')) {
911911
tNode.flags |= TNodeFlags.hasClassInput;
912912
}
913913
if (inputsStore.hasOwnProperty('style')) {

‎packages/core/test/acceptance/styling_spec.ts

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,17 +1129,17 @@ describe('styling', () => {
11291129
});
11301130

11311131
onlyInIvy('only ivy combines static and dynamic class-related attr values')
1132-
.it('should write to a `className` input binding, when static `class` is present', () => {
1132+
.it('should write combined class attribute and class binding to the class input', () => {
11331133
@Component({
11341134
selector: 'comp',
11351135
template: `{{className}}`,
11361136
})
11371137
class Comp {
1138-
@Input() className: string = '';
1138+
@Input('class') className: string = '';
11391139
}
11401140

11411141
@Component({
1142-
template: `<comp class="static" [className]="'my-className'"></comp>`,
1142+
template: `<comp class="static" [class]="'my-className'"></comp>`,
11431143
})
11441144
class App {
11451145
}
@@ -1150,32 +1150,6 @@ describe('styling', () => {
11501150
expect(fixture.debugElement.nativeElement.firstChild.innerHTML).toBe('static my-className');
11511151
});
11521152

1153-
onlyInIvy('in Ivy [class] and [className] bindings on the same element are not allowed')
1154-
.it('should throw an error in case [class] and [className] bindings are used on the same element',
1155-
() => {
1156-
@Component({
1157-
selector: 'comp',
1158-
template: `{{class}} - {{className}}`,
1159-
})
1160-
class Comp {
1161-
@Input() class: string = '';
1162-
@Input() className: string = '';
1163-
}
1164-
@Component({
1165-
template: `<comp [class]="'my-class'" [className]="'className'"></comp>`,
1166-
})
1167-
class App {
1168-
}
1169-
1170-
TestBed.configureTestingModule({declarations: [Comp, App]});
1171-
expect(() => {
1172-
const fixture = TestBed.createComponent(App);
1173-
fixture.detectChanges();
1174-
})
1175-
.toThrowError(
1176-
'[class] and [className] bindings cannot be used on the same element simultaneously');
1177-
});
1178-
11791153
onlyInIvy('only ivy persists static class/style attrs with their binding counterparts')
11801154
.it('should write to a `class` input binding if there is a static class value and there is a binding value',
11811155
() => {
@@ -3475,6 +3449,44 @@ describe('styling', () => {
34753449
expectClass(cmp1).toEqual({foo: true, bar: true});
34763450
expectClass(cmp2).toEqual({foo: true, bar: true});
34773451
});
3452+
3453+
it('should not bind [class] to @Input("className")', () => {
3454+
@Component({
3455+
selector: 'my-cmp',
3456+
template: `className = {{className}}`,
3457+
})
3458+
class MyCmp {
3459+
@Input()
3460+
className: string = 'unbound';
3461+
}
3462+
@Component({template: `<my-cmp [class]="'bound'"></my-cmp>`})
3463+
class MyApp {
3464+
}
3465+
3466+
TestBed.configureTestingModule({declarations: [MyApp, MyCmp]});
3467+
const fixture = TestBed.createComponent(MyApp);
3468+
fixture.detectChanges();
3469+
expect(fixture.nativeElement.textContent).toEqual('className = unbound');
3470+
});
3471+
3472+
it('should not bind class to @Input("className")', () => {
3473+
@Component({
3474+
selector: 'my-cmp',
3475+
template: `className = {{className}}`,
3476+
})
3477+
class MyCmp {
3478+
@Input()
3479+
className: string = 'unbound';
3480+
}
3481+
@Component({template: `<my-cmp class="bound"></my-cmp>`})
3482+
class MyApp {
3483+
}
3484+
3485+
TestBed.configureTestingModule({declarations: [MyApp, MyCmp]});
3486+
const fixture = TestBed.createComponent(MyApp);
3487+
fixture.detectChanges();
3488+
expect(fixture.nativeElement.textContent).toEqual('className = unbound');
3489+
});
34783490
});
34793491
});
34803492

0 commit comments

Comments
 (0)