Angular WebComponent vs Shadow DOM vs rem

Klemens Kühle
coodoo
Published in
4 min readOct 18, 2021

--

Beim Einbinden einer Angular-Komponente als WebComponent mittels Angular Elements ist uns im Live-Test-Betrieb aufgefallen, dass die Styles nicht passen. Dabei dachten wir, eine WebComponent sei doch gut gekapselt, vor allem, wenn wir auf ViewEncapsulation.ShadowDOM setzen. Hier unsere Erkenntnisse über diese Fehlannahme.

PX vs EM vs REM

Um das Thema beim Problem aufzurollen, eine kurze Einführung in gängige CSS-Maßeinheiten. Während px eine absolute Größe angibt, sind em und rem relative Größen. em bezieht sich dabei auf die Größe des Parent-Containers. Bei rem bezieht sich der Wert auf die “root”-Font-Size, also die am html- oder body-Tag gesetzte Schriftgröße.

Im folgenden Screenshot zum Beispiel ist die Base-Font-Size am html-Tag auf 12pt gesetzt (≙16px). Damit ergibt sich für die markierte Überschrift “Bundesliga 2021/22” mit der font-size 1.3rem eine Schriftgröße von 20.8px.

Screenshot aus unserer öffentlichen Tipprunde pub.halbzeit.app

Angular Elements Webcomponents

Wie einleitend beschrieben, ist nun beim Test-Livegang einer WebComponent aufgefallen, dass die Styles abweichen, obwohl es bisher in verschiedenen lokalen Umgebungen immer gepasst hat. Nach ein paar Untersuchungen mittels den Browser-Dev-Tools war der Übeltäter gefunden: Die Schriftgröße eines td-Tags wurde auf 12.25px berechnet, nicht wie erwartet auf 14px.

Die definierte font-size des td-Tags war mit rem angegeben und bezog sich dementsprechend auf die in der eingebundenen Umgebung definierten Base-Font-Size von 14px. In unseren bisherigen Testumgebungen war keine spezielle Schriftgröße angegeben, was dazu führt, dass der Browser diese bestimmt und das ist bei den gängigen Browsern üblicherweise 16px.

Eingebundene WebComponents in verschiedenen Umgebungen

Aber wie konnte das sein? Hatten wir nicht auf WebComponents gebaut, die sich unter anderem dadurch auszeichnen, dass sie sich unabhängig von der einbindenden Umgebung komplett abkapseln? Generell schon, aber ein wichtiges Thema dabei ist noch die richtige ViewEncapsulation.

Angular ViewEncapsulation

ViewEncapsulation-Enum aus der Angular-Core-Library

Bei einer Angular-Component kann man im Component-Decorator angeben, welche ViewEncapsulation diese Komponente haben soll. Dabei gibt es drei auswählbare Werte: Emulated, None und ShadowDom.

Die Default-ViewEncapsulation-Value einer Angular-Component ist dabei ViewEncapsulation.Emulated und kapselt die Styles der Komponente gewohnt mit den _nghost[…]/_ngcontent[…]-Attributen ab. In dieser Kapselung beziehen sich in der Komponente definierte Styles exakt nur auf diese Komponente (wenn man davon ausgeht, dass keine Hacks wie ::ng-deep, /deep/ o.Ä. genutzt werden).

Nutzt man hingegen ViewEncapsulation.None, können die in der Komponente definierten Styles sich auf andere Styles außerhalb dieser auswirken. Es gibt eben gar keine Kapselung.

ViewEncapsulation.ShadowDom erstellt einen nativen Shadow-DOM-root um den Inhalt der Komponente. Dieser schützt jeden Style, der innerhalb der Komponente deklariert ist, nativ davor, auch außerhalb der Komponente zu greifen.

Shadow-DOM bietet also die sicherste Kapselung, perfekt wenn man mittels WebComponents fremden Inhalt in einer komplett anderen Umgebung einbinden will, ohne gegenseitige Beeinflussung… oder nicht?

Wie kommt es dann zu der oben genannten Änderung der Font-Size in der eingebundenen Komponente? CSS-Tricks.com bietet nach langer Suche endlich die ersehnte Erklärung:

[…] There are a few exceptions that inherit from the parent document, like font family and document font sizes (e.g. rem) that can be overridden internally.

Es gibt also Ausnahmen, bei denen Styles von außerhalb sogar in eine Shadow-Dom-Komponente einwirken. “Aus UX-Sicht gerechtfertigt”, wird argumentiert, aber das ist eben subjektiv und anwendungsspezifisch.

Fazit

Selbst innerhalb einer mittels nativem Shadow DOM eingebundenen WebComponent beziehen sich rem-Werte bei CSS Styles auf die Base-Font-Size der einbindenden Seite.

Wer also WebComponents nutzen will, sollte sich dessen bewusst sein und sich vorher mit der einbindenden Seite vertraut machen und ihren Style-Definitionen, die sich auf Fonts beziehen. Wir haben unsere Lektion gelernt!

Trivia

  • Ganz interessant ist der historische Kontext zur Maßeinheit em, wie hier im deutschen Wiki-Eintrag oder zusammenfassend wie jemand auf StackOverflow schreibt: “One em was traditionally defined as the width of the capital "M" in the current typeface and point size, as the ‘M’ was commonly cast the full-width of the square ‘blocks’, or ‘em-quads’ (also ‘mutton-quads’), which are used in printing presses.
  • Dem aufmerksamen Leser mag aufgefallen sein, dass das ViewEncapsulation-Enum den Wert 1 ausgelassen hat. Der Grund dafür ist, dass vor Angular v8 an dieser Stelle der Wert Native stand, der eine nun veraltete Version von nativem Shadow Dom nutzte (siehe https://v8.angular.io/guide/component-styles#view-encapsulation)

Weiterführende Links

--

--