CSS Cascade Layers in Angular verwenden

Wer kennt das nicht: Ihr definiert eine neue CSS-Regel, kompiliert eure Web-Applikation und werft einen Blick auf die Seite im Browser. Was? Warum wurde das Styling nicht angewandt?

Sehr oft ist der Übeltäter eine andere CSS-Regel mit höherer Spezifität. Vielleicht hab ihr die Bibliothek eines Drittanbieters eingebunden, deren Styles in Konflikt mit euren stehen.

Doch Rettung naht in Form eines neuen CSS-Features: CSS Cascade Layer. Diese ermöglichen uns die Aufteilung unserer Styles in streng getrennte Schichten, um Konflikte zu vermeiden. Ich zeige euch, wie ihr das neue Feature in einer Angular-Applikation mit SCSS anwenden könnt.

Eine Torte mit mehreren erkennbaren Schichten. Foto: © Dalila Dalprat / pexels.com

Was sind Cascade Layer?

CSS Cascade Layer geben Autoren mehr Kontrolle über die Kaskade in CSS. Also über die Art und Weise wie Browser entscheiden, welche Styling-Regeln auf ein HTML-Element angewandt werden.

Das neue Feature ist im CSS-Modul Cascading and Inheritance Level 5 definiert. Obwohl das Dokument erst den Entwurf-Status hat, werden Cascade Layer bereits von allen modernen Browsern unterstützt. Das Dokument besagt:

Cascade layers provide a structured way to organize and balance concerns within a single origin. Rules within a single cascade layer cascade together, without interleaving with style rules outside the layer.

Das heißt, wir können mehrere Layer definieren – beginnend bei Styles mit niedriger Priorität (z.B. Bibliotheken von Drittanbietern) bis zu hochprioritären Styles von Komponenten. Konflikte zwischen einzelnen Schichten werden immer so aufgelöst, dass die CSS-Regeln der Schicht mit höchster Priorität angewandt werden.

Wenn wir uns die Schritte in der Kaskade ansehen, dann sehen wir dass die Zugehörigkeit zu einem gewissen Layer geprüft wird bevor die Spezifität und Reihenfolge der Deklaration geprüft wird.

  1. Ursprung und Wichtigkeit (!important)
  2. Kontext
  3. CSS-Regeln im HTML-Element
  4. Layer
  5. Spezifität
  6. Reihenfolge der Deklaration

Bitte schaut euch das Video „The CSS Cascade, a deep dive“ von Bramus Van Damme an, um eine detaillierte Einführung in die CSS-Kaskade zu erhalten.

Die @layer Deklaration

Grundsätzlich deklarieren wir einen neuen Layer inklusive zugehöriger CSS-Regeln mit der @layer Deklaration:

@layer my-layer { // CSS rules }

Ihr könnt auch einen benannten Cascade Layer definieren, ohne diesem CSS-Regeln zuzuweisen. Auf diese Weise könnt ihr die Hierarchie eurer Layer bestimmen. Als Best Practice wird empfohlen, zunächst die Hierarchie der Layer mit @layer zu definieren und danach erst die konkreten CSS-Regeln den Layern hinzuzufügen:

@layer my-layer-1, my-layer-2; @layer my-layer-1 { // CSS rules } @layer my-layer-2 { // CSS rules }

Alle Styles, die nicht innerhalb eines Layers deklariert wurden, werden automatisch als Teil eines anonymen Layers erfasst. Dieser anonyme Layer hat eine höhere Priorität als die explizit definierten Layer. Das bedeutet, dass alle CSS-Regeln, die außerhalb von Layern definiert wurden, Vorrang vor den Styles innerhalb von Layern haben.

Ich gehe hier nicht auf alle Details der Cascade Layer ein. Bitte lest dazu den großartigen Artikel „A Complete Guide to CSS Cascade Layers“.

Demo-Applikation mit Angular und SCSS

Ich habe eine Applikation mit Angular 14 und Leaflet erstellt, welche eine interaktive Karte anzeigt. Im Projekt verwende ich SCSS, da es tolle Features wie Placeholders und Mixins bietet. Seht euch meinen Quellcode hier an.

Mein Ziel ist einfach: Ich will sicherstellen, dass mein Styling für individuelle Komponenten immer angewandt wird. Es sollte Vorrang vor meinem Styling-Reset sowie den CSS-Regeln von Drittanbieter-Bibliotheken haben. Zu diesem Zweck habe ich mehrere Layer in der zentralen CSS-Datei styles.scss definiert:

@use "sass:meta"; @layer reset, thirdparty, overrides; @layer reset { @include meta.load-css("layers/reset/normalize"); } @layer thirdparty { @include meta.load-css("node_modules/leaflet/dist/leaflet.css"); } @layer overrides { @include meta.load-css("layers/overrides/leaflet"); }

Ich verwende die SASS-Funktion load-css, um das CSS aus anderen SCSS-Dateien zu laden und es als Teil eines Cascade Layers zu definieren. Das Reset für User Agent Styles gebe ich in den Layer mit der niedrigsten Priorität. Darauf folgt ein Layer für das Standard-Styling der Leaflet-Karte und ein Layer für das globale Überschreiben von Styles.

Ich definiere keinen Layer für die Styles von einzelnen Komponenten, welche in eigenen SCSS-Dateien deklariert werden. Diese CSS-Regeln werden daher automatisch in einem anonymen Layer gesammelt und erhalten die höchste Priorität. Sehen wir uns ein konkretes Beispiel an.

Styling eines Karten-Popups

In meiner Demo-App liste ich meine 5 Lieblingsplätze in Wien auf. Dieses werden auf einer interaktiven Leaflet-Karte markiert. Beim Klick auf einen Marker öffnet sich ein Popup. Das HTML-Template fav-place-popup.component.html des Popups sieht so aus:

<h2>{{data.name}}</h2> <p *ngFor="let desc of data.description">{{desc}}</p>

In fav-place-popup.component.scss habe ich folgende Styles definiert:

p { font-size: 0.8rem; margin: 0.5em 0; }

Darüber hinaus definiert die Leaflet-Bibliothek folgendes Styling für Textabsätze innerhalb von Karten-Popups:

.leaflet-popup-content p { margin: 18px 0; }

Dank meiner etablierten Layer-Struktur muss ich keine Angst davor haben, dass Leaflets Styling für .leaflet-popup-content p mit meinen Komponenten-Styles für p in Konflikt geraten könnten. Gleichzeitig sorgt Angulars View Encapsulation dafür, dass meine Komponenten-Styles nicht auf alle Textabsätze in meiner Applikation angewandt werden. Genial!

Fazit

Ich liebe Cascade Layer! Sie sind ein mächtiges Werkzeug zur Ordnung von CSS-Styles und helfen, nervige Konflikte aufgrund der Spezifität oder Reihenfolge der Deklaration zu vermeiden.

Ein Problem konnte ich noch nicht lösen: Manche Angular-Bibliotheken (z.B. Angular Material) fügen automatisch Styles im head Tag des HTML-Dokuments ein. Bisher habe ich leider keinen Weg gefunden, um diese Styles innerhalb eines Layers meiner Wahl zu deklarieren. Ich hoffe, dass es in Zukunft eine Lösung für dieses Problem geben wird.

Erstellt am