Best Practices für CSS Scope in Angular-Applikationen
Die neue @scope CSS-At-Regel wird seit Dezember 2025 von allen gängigen Browsern unterstützt.
Dieses großartige neue Feature macht es super einfach, Styles auf bestimmte DOM-Teilbereiche, wie beispielsweise den Inhalt
einer Komponente, anzuwenden.
In der Arbeit implementiere ich hauptsächlich Webanwendungen mit dem Angular-Framework. In den letzten Monaten habe ich begonnen, die View Encapsulation von Angular zu deaktivieren und stattdessen das native CSS-Feature zu verwenden. Nun möchte ich meine Erfahrungen und einige Best Practices mit euch teilen.
Foto: © Aditya Aiyar / pexels.com
Wenn ihr mit CSS Scope nicht vertraut seid, lest bitte zuerst meinen Artikel
„Wird das CSS Scope Feature die View Encapsulation von Angular ersetzen?“.
Darin erkläre ich die Grundlagen der @scope CSS-At-Regel und ihren Einsatz in Angular.
Demo-Projekt: CSS Scope Sandbox
Ich habe ein Projekt mit zwei Angular-Anwendungen erstellt,
die denselben Inhalt anzeigen. Der einzige Unterschied besteht darin, dass die app-without-css-scope die
standardmäßige View Encapsulation des Angular-Frameworks verwendet. Die app-with-css-scope hingegen
deaktiviert diese Funktion und verwendet stattdessen die @scope Regel.
Hier ist die deployte Anwendung mit CSS Scope. Schaut euch den Code mit den Entwicklertools eures Browsers an:
Ich finde es toll, wie übersichtlich und aufgeräumt das DOM aussieht. Es ist viel einfacher, die Seitenstruktur zu verstehen und die Styles eines Elements zu überprüfen.
Die CSS-Selektoren, die ihr in den Browser-Entwicklertools seht, stimmen mit denen überein, die ihr im Quellcode definiert
habt. Keine seltsamen Ergänzungen von benutzerdefinierten Attributen wie _ngcontent-xxx mehr.
Was sind also die Best Practices für CSS Scope, die ich empfehlen würde?
Best Practice 1: Effiziente Donut Scopes
Zunächst müssen wir die View Encapsulation für jede unserer Komponenten deaktivieren. Hier ist ein Beispiel aus meiner Demo:
@Component({
selector: 'app-beer-item-list',
encapsulation: ViewEncapsulation.None,
...
})
export class BeerItemList { ... }
Jetzt werden die CSS-Selektoren nicht mehr mit benutzerdefinierten Attributen erweitert und die Komponenten-Styles werden global
angewendet. Als Nächstes müssen wir stattdessen die @scope At-Regel verwenden, um die Styles zu kapseln.
@scope (app-beer-item-list) {
:scope {
display: grid;
...
}
}
Dadurch wird das Containerelement app-beer-item-list der Komponente als Scoping Root
definiert, welche die obere Grenze des Teilbaums bestimmt, auf den wir abzielen.
Aber was ist mit den Inhalten, die von den Kind-Komponenten im Template gerendert werden? Die Komponente BeerItemList
enthält die Kind-Komponente <app-beer-item-details> . Wir möchten nicht, dass die Styles der
übergeordneten Komponente den Inhalt ihrer Kind-Komponenten beeinflusst.
Mit der @scope At-Regel können wir ein Scoping Limit definieren, das die untere
Grenze festlegt. Diese Art von Scope – mit einer oberen und einer unteren Grenze – wird als
Donut Scope bezeichnet:
@scope (app-beer-item-list) to (app-beer-item-details > *) {
:scope {
display: grid;
...
}
app-beer-item-details {
background: var(--canvas-bg-color);
...
}
}
Dieser Scope umfasst das <app-beer-item-details> Container-Element, schließt jedoch den Inhalt der
Kind-Komponente aus.
Was passiert, wenn ihr eine Komponente stylt, die mehrere Kind-Komponenten im Template enthält? Die App-Komponente in meiner Demo
enthält beispielsweise die Kind-Komponenten AppFooter , AppHeader
und BeerItemList . Ihr müsstet jede Kind-Komponente als untere Grenze definieren, was sehr schnell lästig
werden würde.
Um die Verwendung von CSS-Scope zu vereinfachen, habe ich die Angular-Direktive CustomNgHostDirective
erstellt. Die Direktive fügt das benutzerdefinierte Attribut data-ng-host zum Container-HTML-Element einer
Komponente hinzu. Wendet die Direktive einfach mit hostDirectives auf eine Komponente an:
@Component({
selector: 'app-beer-item-details',
encapsulation: ViewEncapsulation.None,
hostDirectives: [CustomNgHostDirective],
...
})
export class BeerItemDetails { ... }
Jetzt können wir das Attribut data-ng-host für eine effiziente Definition der unteren Grenze
einer @scope At-Regel verwenden. Zum Beispiel:
@scope (app-beer-item-list) to ([data-ng-host] > *) {
:scope {
display: grid;
...
}
app-beer-item-details {
background: var(--canvas-bg-color);
...
}
}
Best Practice 2: Ohne ::ng-deep in den Untiefen des DOMs wühlen
Wenn ihr die View Encapsulation von Angular verwendet, gelten Komponenten-Styles normalerweise nur für das HTML im eigenen
Template der Komponente. Mit der Pseudoklasse ::ng-deep könnt ihr die View Encapsulation für eine
bestimmte Regel deaktivieren. Diese Pseudoklasse ist jedoch seit einiger Zeit
deprecated und sollte nicht mehr verwendet werden.
Mit CSS Scopes übernehmt ihr wieder die Kontrolle! Ihr könnt den Gültigkeitsbereich so definieren, dass ihr beispielsweise die internen Styles einer Drittanbieter-Bibliothek sicher überschreiben könnt.
Beispiel: Angenommen, ihr verwendet eine Angular Material-Tabelle, um tabellarische Daten anzuzeigen. Ihr seid mit dem Design weitgehend zufrieden, möchtet aber die Hintergrundfarben ein wenig anpassen. So könnten die Styles eurer Komponente aussehen:
@scope (app-fancy-table) {
table[mat-table] {
thead tr {
background-color: lightblue;
}
}
}
Best Practice 3: Geteilte Styles als globale CSS-Klassen definieren
Ihr müsst nicht alle eure Styles mit CSS Scope in der CSS-Datei einer Komponente definieren. Besser ist, wenn ihr mehrmals
genutzte Styles in globalen Dateien, z. B. im src/styles Ordner, ablegt und sie für die gesamte
Anwendung bereitstellt.
Ich persönlich bevorzuge es, SCSS-Dateien mit allgemeinen Styles für jeden Inhaltstyp oder Element zu erstellen. Zum
Beispiel: _details.scss , _forms.scss und _table.scss .
Sie enthalten Styles für bestimmte HTML-Selektoren wie etwa details sowie globale CSS-Klassen wie
z.B. .default-expansion-panel .
Um es den Komponenten zu erleichtern, diese allgemeinen Styles bei Bedarf zu überschreiben, habe ich sie in einem eigenen Cascade-Layer abgelegt:
@use "sass:meta";
@layer reset, general, components;
@layer general {
@include meta.load-css("base/details");
@include meta.load-css("base/forms");
@include meta.load-css("base/table");
}
Erfahrt mehr über Cascade Layers in meinem Artikel „CSS Cascade Layers in Angular verwenden“.
Alternativ könnt ihr auch SCSS-Features wie Platzhalter oder
Mixins verwenden, um wiederverwendbare Styles zu erstellen. Ich kann bestätigen,
dass das Einfügen eines Mixins mit @include auch im Kontext der nativen @scope
At-Regel funktioniert.
Leider hat die Verwendung eines SCSS-Platzhalters mit @extend für mich im Kontext von CSS Scope nicht
funktioniert. Der SCSS-Präprozessor berücksichtigt offenbar noch nicht die @scope At-Regel, was zu ungültigen
Styles führt. Zumindest war das bei mir mit Angular 21 der Fall. Vielleicht wird das in Zukunft noch behoben.
Fazit
Wie ihr seht, ist es ziemlich einfach, die View Encapsulation von Angular zu deaktivieren und Komponenten-Styles innerhalb
einer @scope At-Regel zu definieren. Der generierte HTML- und CSS-Code ist viel besser lesbar und lässt
sich viel einfacher debuggen.
Oder mit anderen Worten: Angulars View Encapsulation ist tot! Lang lebe CSS Scope! 🤩
Erstellt am