Das Ziel von Web Components liegt darin, wiederverwendbare Komponenten für das Web zu bauen. Damit diese sauber wiederverwendbar sind, liegt das Augenmerk primär auf der Eigenschaft der Kapselung.
Web Components besteht dabei primär aus drei Web-APIs, um diese Ziele zu erreichen:
Doch bevor wir uns mit einem Beispiel in die Praxis der einzelnen Bestandteile wagen, sollten wir ein gemeinsames Verständnis des Begriffs „Web-Komponente“ erlangen.
Komponenten im Web
Eigentlich kommt keine Webanwendung ohne HTML aus, egal ob serverseitig
generiert oder im Client per JavaScript erzeugt. Unser Browser ist darauf
spezialisiert, HTML zu parsen und die dort definierten Elemente grafisch
darzustellen. So entsteht aus einem <input type="text"/>
ein Eingabefeld oder
das <video>
-Element spielt ein Video ab und bietet uns zudem
Eingabemöglichkeiten zum Pausieren oder Spulen an.
HTML bringt dabei nur eine begrenzte Anzahl von Elementen mit. Unsere Aufgabe besteht nun darin, aus diesen Basis-Komponenten eigene, höherwertige Komponenten zu erstellen.
Für diesen Artikel wollen wir dem Benutzer unserer Anwendung Feedback anzeigen. Dieses Feedback besteht aus einem Titel, Text und einem Typ. Sicherlich gibt es mehrere mögliche Lösungen, so eine Komponente in HTML abzubilden. Listing 1 zeigt eine dieser Lösungen, welche die Basis für diesen Artikel bildet.
Zu einer Komponente gehört neben diesem Markup fast immer auch Styling. Da wir im Web unterwegs sind, nutzen wir zwei CSS-Regeln (s. Listing 2), um einen roten oder grünen Rahmen, je nach Typ, um die gesamte Komponente zu ziehen.
Der dritte, optionale Bestandteil einer Komponente im Web besteht aus Verhalten und wird per JavaScript umgesetzt. Wir möchten dem Benutzer die Möglichkeit geben, die Benachrichtigung mit dem Klick auf einen Button zu schließen. Dies können wir zum Beispiel mit dem Code aus Listing 3 umsetzen. Wir fügen hierzu per JavaScript einen Button hinzu und sorgen dafür, dass bei einem Klick auf diesen die gesamte Komponente per CSS-Styling versteckt wird.
Der Button wird per JavaScript hinzugefügt und ist nicht bereits Teil des Markups, da dieser ohne JavaScript keine Funktionalität besitzt und dem Benutzer deswegen nicht hilft. Wir realisieren hier somit das Prinzip von Progressive Enhancement. Sollte das JavaScript, aus welchem Grund auch immer, nicht ausgeführt werden, kann der Benutzer die Benachrichtigung nicht schließen. Die Kernfunktionalität, das Anzeigen des Titels und Textes, wird allerdings nicht beeinträchtigt.
Bereits jetzt wird klar, dass jemand Fremdes, sollte er unsere Komponente
wiederverwenden wollen, vieles beachten muss. Zum einen muss er sich an das von
uns vorgegebene Markup halten. Zumindest die Klassen alert
und alert-*
sind
verpflichtend, da ansonsten weder das Styling noch das Verhalten funktioniert.
Zudem muss er daran denken, CSS und JavaScript auch an der passenden Stelle mit
einzubinden.
Im Folgenden wollen wir uns anschauen, wie eine identische Komponente mit Web Components umgesetzt werden kann.
Custom Elements
Custom Elements ermöglichen es uns, anstatt des semantisch wenig
aussagekräftigen div
-Elements mit einer Klasse alert
ein eigenes
HTML-Element zu verwenden (s. Listing 4). Da dieses Element für den Browser noch
unbekannt ist, rendert er dieses erstmal relativ neutral. Das Element my-alert
wird somit ähnlich wie ein span
behandelt und auch dargestellt.
Um dem Browser nun mitzuteilen, wie er mit dem unbekannten Element umgehen soll,
müssen wir es ihm bekannt machen. Hierzu wird per JavaScript die
CustomElementRegistry
genutzt (s. Listing 5). Das erste Argument entspricht
dabei dem Namen, unter dem das Custom Element anschließend im HTML referenziert
wird. Es ist zu beachten, dass stets mindestens ein -
im Namen enthalten sein
muss. Als zweites Argument wird eine JavaScript-Klasse übergeben.
Immer wenn der Browser nun ein vorher definiertes Custom Element findet, erzeugt er eine Instanz der übergebenen Klasse. Zusätzlich gibt es einen definierten Lebenszyklus, der sich darin äußert, dass der Browser bei bestimmten Events definierte Methoden auf der vorher erzeugten Instanz aufruft.
Die Methode connectedCallback
wird immer aufgerufen, wenn das Element an einen
Knoten im DOM angehangen wird, disconnectedCallback
wenn es entfernt wird.
adoptedCallback
ist für das Verschieben des Elements vorgesehen und
attributeChangedCallback
wird immer aufgerufen, wenn sich eines der Attribute
des Elements ändert.
In unserem Beispiel können wir den connectedCallback
nutzen, um den Button zu
erzeugen, dessen Logik zu implementieren und ihn anschließend in den DOM
einzuhängen (s. Listing 6). Da unsere Komponente das generische HTMLElement
erweitert und auch ansonsten keine besondere Funktionalität eines bestehenden
HTML-Elements wiederverwendet werden soll, handelt es sich um ein sogenanntes
Autonomous Custom Element.
Möchten wir ein Custom Element schreiben, das sich wie eine Liste verhält, benötigen wir ein Customized Built-in-Element. Um ein solches zu verwenden, müssen drei Stellen in Listing 6 geändert werden.
Zuerst muss unsere eigene Klasse nicht mehr von HTMLElement
, sondern vom
passenden spezifischen Element, zum Beispiel HTMLUListElement
, erben. Zudem
muss auch beim Registrieren des Custom Element mit angegeben werden, welches
Element von der Komponente erweitert wird (s. Listing 7).
Die dritte Änderung entsteht bei der Verwendung einer so definierten Komponente.
Anstatt den definierten Namen als Element-Namen zu verwenden, nutzen wir das
Attribut is
(s. Listing 8).
Shadow DOM
Der zweite Bestandteil von Web Components ist der Shadow DOM. Mit diesem ist es möglich, an ein beliebiges Element einen parallelen und versteckten DOM-Baum anzufügen. Dabei entsteht eine hohe Kapselung zwischen diesem Shadow DOM und dem eigentlichen DOM. Listing 9 zeigt die Verwendung des Shadow DOM für unsere Beispielkomponente.
Sobald ein Shadow DOM an ein Element angehängt wird, werden alle Kinder des Elementes nicht mehr angezeigt. Deshalb müssen wir in unserer Komponente nun nicht mehr nur den Button hinzufügen, sondern auch die eigentlichen Kinder in den Shadow DOM kopieren.
Elemente innerhalb des so entstandenen Shadow DOMs sind von JavaScript aus nun
nur noch explizit erreichbar oder zu finden. Versuchen wir zum Beispiel, den
Button mit document.querySelectorAll('button')
zu selektieren, erhalten wir
eine leere Liste. Es ist allerdings weiterhin möglich, den Button zu erreichen,
indem wir direkt auf den Shadow Root des Elements zugreifen (z. B. durch
document.querySelector('my-alert').shadowRoot.querySelector('button')
).
Ein direkt sichtbares Ergebnis dieser Zugriffsregel ist, dass Elemente im Shadow DOM auch nicht mehr von im DOM definierten CSS-Regeln beeinflusst werden. Entfernen wir beispielsweise Zeile 22 aus Listing 9 werden keine Rahmen mehr angezeigt.
Andersherum ermöglicht die Kapselung es, innerhalb des Shadow DOM ein
style
-Element mit CSS hinzuzufügen, ohne dass diese Regeln Elemente außerhalb
des Shadow DOM betreffen. Somit würde ein zusätzliches <div class="warn">
im
HTML auch weiterhin ohne Rahmen angezeigt.
Neben dem in Zeile 4 aus Listing 9 verwendeten Mode open
gibt es auch noch
eine weitere Variante: closed
. Geben wir diese beim Erzeugen des Shadow DOM
an, wird der JavaScript-seitige Zugriff auf den Shadow DOM noch weiter
erschwert.
Ändern wir die Komponente aus Listing 9 auf die in Listing 10 gezeigte Variante,
funktioniert auch der direkte Zugriff auf den Button nicht mehr, da
document.querySelector('my-alert').shadowRoot
bereits null
zurückliefert.
Das explizite Merken des Shadow DOM in einer eigenen Variablen ist hier
notwendig, da der closed
-Mode den Zugriff auch innerhalb der eigenen
Komponente verhindert.
HTML Templates
Das dritte und letzte Web-API, das zu Web Components gehört, sind HTML
Templates. Diese bestehen aus den beiden HTML-Elementen template
und slot
.
Das template
-Element existiert bereits seit Längerem und wird dazu verwendet,
einen HTML-Schnipsel auszugeben, ohne dass der Browser diesen interpretiert.
Listing 11 definiert ein Template für unsere Komponente. Dieses Template kann
nun innerhalb unseres Custom Element verwendet werden (s. Listing 12).
Der eigentliche Inhalt des Elementes my-alert
wird somit durch den des
Templates ersetzt, und anstelle einer h4
für den Titel wird nun das h1
genutzt. Zudem wird der Inhalt des style
-Elements nur innerhalb unserer
Komponente ausgewertet und weitere h1
-Elemente auf der Seite sind von der
roten Schrift nicht betroffen.
Der zweite Bestandteil von HTML Templates ist das slot
-Element. Mit diesem
wurde ein rudimentärer Template-Mechanismus für den Shadow DOM implementiert.
Die Änderung unserer Komponente auf das Template von Listing 11 führt nun dazu,
dass alle Benachrichtigungen auf der Seite denselben Titel und Text enthalten.
Es soll jedoch jede Benachrichtigung einen individuellen Titel und Text erhalten
können. Hierzu erweitern wir das Template um zwei slot
-Elemente (s. Listing
13).
Definieren wir nun unsere Benachrichtigungen auf der Seite wie in Listing 14,
dann ersetzt der Browser beim Anzeigen der Seite die beiden slot
-Elemente
durch die Werte innerhalb des Custom Element.
Fazit
In diesem Artikel haben wir gemeinsam die Welt der Web Components anhand einer beispielhaften Komponente erkundet. Web Components bestehen dabei aus den drei Spezifikationen Custom Elements, Shadow DOM und HTML Templates.
Kombinieren wir diese Spezifikationen, ist es möglich, eigene HTML-Elemente zu erzeugen, die zudem ihre Implementierungsdetails vor ihrer Umwelt verstecken, also wegkapseln. Hierdurch ist es möglich, Komponenten zu erstellen, die anschließend auch in anderen Anwendungen wiederverwendet werden können.
Für generische Komponenten gibt es bereits mehrere Anbieter, bei denen wir fertige Komponenten finden und in unsere Anwendung integrieren können. Zu diesen Anbietern gehören unter anderem:
Bei den meisten Features, die von Browsern umgesetzt werden müssen, dauert es ein wenig, bis diese flächendeckend zur Verfügung stehen und somit benutzbar sind.
Betrachten wir doch einmal die Statistiken auf „Can I Use“ zu Custom Elements, Shadow DOM und HTML Templates. Es zeigt sich, dass alle drei Features in den meisten modernen Browsern bereits angekommen sind. Lediglich im Edge von Microsoft fehlen die Features noch. Allerdings gibt es für beides Polyfills (s. Custom Element Polyfill und Shadow DOM Polyfill).