diff --git a/packages/core/src/render3/styling/class_and_style_bindings.ts b/packages/core/src/render3/styling/class_and_style_bindings.ts index a22b096772..e1d5325ea9 100644 --- a/packages/core/src/render3/styling/class_and_style_bindings.ts +++ b/packages/core/src/render3/styling/class_and_style_bindings.ts @@ -982,14 +982,17 @@ function setClass( if (playerBuilder) { playerBuilder.setValue(className, add); } - } else if (add) { - ngDevMode && ngDevMode.rendererAddClass++; - isProceduralRenderer(renderer) ? renderer.addClass(native, className) : - native['classList'].add(className); - } else { - ngDevMode && ngDevMode.rendererRemoveClass++; - isProceduralRenderer(renderer) ? renderer.removeClass(native, className) : - native['classList'].remove(className); + // DOMTokenList will throw if we try to add or remove an empty string. + } else if (className !== '') { + if (add) { + ngDevMode && ngDevMode.rendererAddClass++; + isProceduralRenderer(renderer) ? renderer.addClass(native, className) : + native['classList'].add(className); + } else { + ngDevMode && ngDevMode.rendererRemoveClass++; + isProceduralRenderer(renderer) ? renderer.removeClass(native, className) : + native['classList'].remove(className); + } } } diff --git a/packages/core/test/linker/regression_integration_spec.ts b/packages/core/test/linker/regression_integration_spec.ts index 5deff6ad2e..e46ba87c19 100644 --- a/packages/core/test/linker/regression_integration_spec.ts +++ b/packages/core/test/linker/regression_integration_spec.ts @@ -317,6 +317,13 @@ function declareTests(config?: {useJit: boolean}) { expect(ctx.componentInstance.viewContainers.first).toBe(vc); }); + it('should not throw when encountering an empty class attribute', () => { + const template = '
'; + TestBed.overrideComponent(MyComp1, {set: {template}}); + + expect(() => TestBed.createComponent(MyComp1)).not.toThrow(); + }); + describe('empty templates - #15143', () => { it('should allow empty components', () => { @Component({template: ''})