From d47a197a9b2ae789875aa35d2fa9663df2d5fd7c Mon Sep 17 00:00:00 2001 From: desig9stein Date: Fri, 13 Mar 2026 12:04:10 +0200 Subject: [PATCH 1/3] feat(style-inheritance-test): implement and compare different style inheritance strategies Add components to demonstrate three approaches to style inheritance: 1. `styleUrls` array (duplicated styles). 2. Single `styleUrl` (no inheritance, missing styles). 3. Adopted StyleSheets API (shared styles without duplication). Includes supporting shared services, directives, and detailed documentation. --- src/app/app.routes.ts | 5 + src/app/style-inheritance-test/RESEARCH.md | 204 +++++++ .../base/base.component.css | 41 ++ .../base/base.component.ts | 26 + .../child-a/child-a.component.css | 27 + .../child-a/child-a.component.ts | 33 ++ .../child-c/child-c.component.css | 37 ++ .../child-c/child-c.component.ts | 32 ++ .../child-d/child-d.component.ts | 64 +++ .../child-e/child-e.component.ts | 64 +++ .../shared-styles.service.ts | 88 +++ .../style-inheritance-test.component.ts | 509 ++++++++++++++++++ .../styled-base.directive.ts | 51 ++ 13 files changed, 1181 insertions(+) create mode 100644 src/app/style-inheritance-test/RESEARCH.md create mode 100644 src/app/style-inheritance-test/base/base.component.css create mode 100644 src/app/style-inheritance-test/base/base.component.ts create mode 100644 src/app/style-inheritance-test/child-a/child-a.component.css create mode 100644 src/app/style-inheritance-test/child-a/child-a.component.ts create mode 100644 src/app/style-inheritance-test/child-c/child-c.component.css create mode 100644 src/app/style-inheritance-test/child-c/child-c.component.ts create mode 100644 src/app/style-inheritance-test/child-d/child-d.component.ts create mode 100644 src/app/style-inheritance-test/child-e/child-e.component.ts create mode 100644 src/app/style-inheritance-test/shared-styles.service.ts create mode 100644 src/app/style-inheritance-test/style-inheritance-test.component.ts create mode 100644 src/app/style-inheritance-test/styled-base.directive.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 209146558d0..ee05d69d968 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,4 +1,5 @@ import { TreeGridAddRowSampleComponent } from './tree-grid-add-row/tree-grid-add-row.sample'; +import { StyleInheritanceTestComponent } from './style-inheritance-test/style-inheritance-test.component'; import { Routes } from '@angular/router'; import { AvatarSampleComponent } from './avatar/avatar.sample'; import { BadgeSampleComponent } from './badge/badge.sample'; @@ -729,5 +730,9 @@ export const appRoutes: Routes = [ { path: 'labelDirective', component: LabelSampleComponent + }, + { + path: 'style-inheritance-test', + component: StyleInheritanceTestComponent } ]; diff --git a/src/app/style-inheritance-test/RESEARCH.md b/src/app/style-inheritance-test/RESEARCH.md new file mode 100644 index 00000000000..55db349b50d --- /dev/null +++ b/src/app/style-inheritance-test/RESEARCH.md @@ -0,0 +1,204 @@ +# Research: Avoiding Style Duplication with Adopted StyleSheets + +## The Problem +When Component A and Component C extend Component B, and only A and C are used on the page: +- Using `styleUrls` array duplicates base styles in each child's CSS bundle +- Angular doesn't natively inherit styles through class extension + +## Solution: Constructable StyleSheets (Adopted StyleSheets API) + +### What Are Adopted StyleSheets? +The `adoptedStyleSheets` API allows you to create a `CSSStyleSheet` object in JavaScript and share it across multiple Shadow DOMs or the document. **The same stylesheet instance is shared, not duplicated.** + +```javascript +// Create a stylesheet once +const sheet = new CSSStyleSheet(); +sheet.replaceSync('.base-container { padding: 20px; }'); + +// Share it across multiple shadow roots +element1.shadowRoot.adoptedStyleSheets = [sheet]; +element2.shadowRoot.adoptedStyleSheets = [sheet]; +// Same sheet object, no duplication! +``` + +### Browser Support (as of 2026) +- ✅ Chrome 73+ +- ✅ Edge 79+ +- ✅ Firefox 101+ +- ✅ Safari 16.4+ + +--- + +## Implementation Approaches + +### Approach 1: Shared StyleSheet Service +Create a service that provides shared CSSStyleSheet instances: + +```typescript +@Injectable({ providedIn: 'root' }) +export class SharedStylesService { + private baseStyles: CSSStyleSheet; + + constructor() { + this.baseStyles = new CSSStyleSheet(); + this.baseStyles.replaceSync(` + .base-container { padding: 20px; border: 2px solid #3f51b5; } + .base-button { padding: 10px 20px; background: #3f51b5; } + `); + } + + getBaseStyles(): CSSStyleSheet { + return this.baseStyles; + } +} +``` + +### Approach 2: Component with Shadow DOM + Adopted StyleSheets +```typescript +@Component({ + selector: 'app-child-a', + template: `...`, + encapsulation: ViewEncapsulation.ShadowDom +}) +export class ChildAComponent implements OnInit { + private sharedStyles = inject(SharedStylesService); + private elementRef = inject(ElementRef); + + ngOnInit() { + const shadowRoot = this.elementRef.nativeElement.shadowRoot; + shadowRoot.adoptedStyleSheets = [ + this.sharedStyles.getBaseStyles(), + this.childStyles // child-specific styles + ]; + } +} +``` + +### Approach 3: Base Class with Style Injection (Directive Pattern) +```typescript +@Directive() +export abstract class StyledBaseDirective implements OnInit { + protected elementRef = inject(ElementRef); + protected sharedStyles = inject(SharedStylesService); + + ngOnInit() { + this.applySharedStyles(); + } + + protected applySharedStyles() { + const shadowRoot = this.elementRef.nativeElement.shadowRoot; + if (shadowRoot) { + const existingStyles = [...shadowRoot.adoptedStyleSheets]; + shadowRoot.adoptedStyleSheets = [ + this.sharedStyles.getBaseStyles(), + ...existingStyles + ]; + } + } +} +``` + +--- + +## What Other Libraries/Frameworks Do + +### 1. Lit (Google's Web Components Library) +Lit uses `adoptedStyleSheets` by default for style sharing: +```javascript +class MyElement extends LitElement { + static styles = css`.base { padding: 20px; }`; +} +// Styles are automatically shared via adoptedStyleSheets +``` + +### 2. Stencil.js +Uses constructable stylesheets when available, falls back to `