Skip to content

Commit 7b64680

Browse files
committed
fix(ivy): ensure map-based interpolation works with other map-based sources (angular#33236)
Prior to this fix if a map-based class or style binding wrote its values onto an elemenent, the internal styling context would not register the binding if the initial value as a `NO_CHANGE` value. This situation occurs if a directive takes control of the `class` or `style` input values and then returns a `NO_CHANGE` value if the initial value is empty. This patch ensures that all bindings are always registered with the `TStylingContext` data-structure even if their initial value is an instance of `NO_CHANGE`. PR Close angular#33236
1 parent d5b5900 commit 7b64680

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

packages/core/src/render3/styling/bindings.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ export function updateClassViaContext(
6060
const isMapBased = !prop;
6161
const state = getStylingState(element, directiveIndex);
6262
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++;
63-
if (value !== NO_CHANGE) {
63+
const hostBindingsMode = isHostStylingActive(state.sourceIndex);
64+
65+
// even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngClass])
66+
// then we still need to register the binding within the context so that the context
67+
// is aware of the binding before it gets locked.
68+
if (!isContextLocked(context, hostBindingsMode) || value !== NO_CHANGE) {
6469
const updated = updateBindingData(
6570
context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate,
6671
false);
@@ -95,7 +100,12 @@ export function updateStyleViaContext(
95100
const isMapBased = !prop;
96101
const state = getStylingState(element, directiveIndex);
97102
const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++;
98-
if (value !== NO_CHANGE) {
103+
const hostBindingsMode = isHostStylingActive(state.sourceIndex);
104+
105+
// even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngStyle])
106+
// then we still need to register the binding within the context so that the context
107+
// is aware of the binding before it gets locked.
108+
if (!isContextLocked(context, hostBindingsMode) || value !== NO_CHANGE) {
99109
const sanitizationRequired = isMapBased ?
100110
true :
101111
(sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false);

packages/core/test/acceptance/styling_spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,6 +2292,23 @@ describe('styling', () => {
22922292
fixture.detectChanges();
22932293
}).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/);
22942294
});
2295+
2296+
it('should properly merge class interpolation with class-based directives', () => {
2297+
@Component(
2298+
{template: `<div class="zero {{one}}" [class.two]="true" [ngClass]="'three'"></div>`})
2299+
class MyComp {
2300+
one = 'one';
2301+
}
2302+
2303+
const fixture =
2304+
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
2305+
fixture.detectChanges();
2306+
2307+
expect(fixture.debugElement.nativeElement.innerHTML).toContain('zero');
2308+
expect(fixture.debugElement.nativeElement.innerHTML).toContain('one');
2309+
expect(fixture.debugElement.nativeElement.innerHTML).toContain('two');
2310+
expect(fixture.debugElement.nativeElement.innerHTML).toContain('three');
2311+
});
22952312
});
22962313

22972314
function assertStyleCounters(countForSet: number, countForRemove: number) {

0 commit comments

Comments
 (0)