diff --git a/packages/core/src/render3/styling/bindings.ts b/packages/core/src/render3/styling/bindings.ts index b4145ec23a..8d9453e237 100644 --- a/packages/core/src/render3/styling/bindings.ts +++ b/packages/core/src/render3/styling/bindings.ts @@ -60,7 +60,12 @@ export function updateClassViaContext( const isMapBased = !prop; const state = getStylingState(element, directiveIndex); const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.classesIndex++; - if (value !== NO_CHANGE) { + const hostBindingsMode = isHostStylingActive(state.sourceIndex); + + // even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngClass]) + // then we still need to register the binding within the context so that the context + // is aware of the binding before it gets locked. + if (!isContextLocked(context, hostBindingsMode) || value !== NO_CHANGE) { const updated = updateBindingData( context, data, countIndex, state.sourceIndex, prop, bindingIndex, value, forceUpdate, false); @@ -95,7 +100,12 @@ export function updateStyleViaContext( const isMapBased = !prop; const state = getStylingState(element, directiveIndex); const countIndex = isMapBased ? STYLING_INDEX_FOR_MAP_BINDING : state.stylesIndex++; - if (value !== NO_CHANGE) { + const hostBindingsMode = isHostStylingActive(state.sourceIndex); + + // even if the initial value is a `NO_CHANGE` value (e.g. interpolation or [ngStyle]) + // then we still need to register the binding within the context so that the context + // is aware of the binding before it gets locked. + if (!isContextLocked(context, hostBindingsMode) || value !== NO_CHANGE) { const sanitizationRequired = isMapBased ? true : (sanitizer ? sanitizer(prop !, null, StyleSanitizeMode.ValidateProperty) : false); diff --git a/packages/core/test/acceptance/styling_spec.ts b/packages/core/test/acceptance/styling_spec.ts index ab0bcf2d99..25a4f19a7f 100644 --- a/packages/core/test/acceptance/styling_spec.ts +++ b/packages/core/test/acceptance/styling_spec.ts @@ -2292,6 +2292,23 @@ describe('styling', () => { fixture.detectChanges(); }).toThrowError(/ExpressionChangedAfterItHasBeenCheckedError/); }); + + it('should properly merge class interpolation with class-based directives', () => { + @Component( + {template: `
`}) + class MyComp { + one = 'one'; + } + + const fixture = + TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp); + fixture.detectChanges(); + + expect(fixture.debugElement.nativeElement.innerHTML).toContain('zero'); + expect(fixture.debugElement.nativeElement.innerHTML).toContain('one'); + expect(fixture.debugElement.nativeElement.innerHTML).toContain('two'); + expect(fixture.debugElement.nativeElement.innerHTML).toContain('three'); + }); }); function assertStyleCounters(countForSet: number, countForRemove: number) {