From 0b35e23d77c4a13ade06114892f18693720a93e0 Mon Sep 17 00:00:00 2001 From: Chintan Kavathia Date: Wed, 4 Sep 2024 12:46:29 +0530 Subject: [PATCH] fix(core): preserve static class/styles on root component Fix to preserve statically applied classes and styles on root component while using host binding. Fixes #51460 --- packages/core/src/render3/component_ref.ts | 27 +++++++++++++++++-- .../core/test/acceptance/bootstrap_spec.ts | 26 ++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 6b3810e74597c..c8d4ebae53f90 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -57,6 +57,7 @@ import {ComponentDef, DirectiveDef, HostDirectiveDefs} from './interfaces/defini import {InputFlags} from './interfaces/input_flags'; import { NodeInputBindings, + TAttributes, TContainerNode, TElementContainerNode, TElementNode, @@ -89,6 +90,7 @@ import {getComponentLViewByIndex, getNativeByTNode, getTNode} from './util/view_ import {ViewRef} from './view_ref'; import {ChainedInjector} from './chained_injector'; import {unregisterLView} from './interfaces/lview_tracking'; +import {AttributeMarker} from './interfaces/attribute_marker'; export class ComponentFactoryResolver extends AbstractComponentFactoryResolver { /** @@ -499,10 +501,29 @@ function createRootComponentTNode(lView: LView, rNode: RNode): TElementNode { ngDevMode && assertIndexInRange(lView, index); lView[index] = rNode; + let attr: TAttributes | null = null; + if (rNode && 'classList' in rNode && (rNode as any).classList?.length) { + attr = [AttributeMarker.Classes, ...(Array.from((rNode as any).classList) as string[])]; + } + if (rNode && 'getAttribute' in rNode && (rNode as any).getAttribute('style')?.length) { + attr = attr?.length ? [...attr, AttributeMarker.Styles] : [AttributeMarker.Styles]; + const styleAttribute = (rNode as any).getAttribute('style'); + + const styles: string[] = styleAttribute.split(';'); + + styles.forEach((style) => { + const [property, value] = style.split(':').map((s) => s.trim()); + + if (property && value) { + attr!.push(property, value); + } + }); + } + // '#host' is added here as we don't know the real host DOM name (we don't want to read it) and at // the same time we want to communicate the debug `TNode` that this is a special `TNode` // representing a host element. - return getOrCreateTNode(tView, index, TNodeType.Element, '#host', null); + return getOrCreateTNode(tView, index, TNodeType.Element, '#host', attr); } /** @@ -576,7 +597,9 @@ function applyRootComponentStyling( for (const def of rootDirectives) { tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs); } - + if (tNode.attrs !== null) { + tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, tNode.attrs); + } if (tNode.mergedAttrs !== null) { computeStaticStyling(tNode, tNode.mergedAttrs, true); diff --git a/packages/core/test/acceptance/bootstrap_spec.ts b/packages/core/test/acceptance/bootstrap_spec.ts index 004f5f9ae66c9..239d2e6b1c448 100644 --- a/packages/core/test/acceptance/bootstrap_spec.ts +++ b/packages/core/test/acceptance/bootstrap_spec.ts @@ -21,6 +21,7 @@ import { ViewEncapsulation, ɵNoopNgZone, ɵZONELESS_ENABLED, + ElementRef, } from '@angular/core'; import {bootstrapApplication, BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -82,6 +83,31 @@ describe('bootstrap', () => { }), ); + it( + 'should preserve static class and styles on root component', + withBody('', async () => { + @Component({ + selector: 'test-cmp', + standalone: true, + template: '(test)', + host: { + class: 'baar', + style: 'width: 100%;', + }, + }) + class TestCmp { + constructor(public element: ElementRef) {} + } + const appRef = await bootstrapApplication(TestCmp); + expect(appRef.components[0].instance.element.nativeElement.className).toBe('baar foo'); + expect(appRef.components[0].instance.element.nativeElement.getAttribute('style')).toBe( + 'width: 100%; height: 100%;', + ); + + appRef.destroy(); + }), + ); + describe('options', () => { function createComponentAndModule( options: {