Der lise Blog:
Einsichten, Ansichten, Aussichten

ngContent, ngTemplate, ngTemplateOutlet: Wiederverwendbare Komponenten in Angular 7

Wer kennt sie nicht? Komponentenbibliotheken wie primeNG, ngBootstrap, Angular Material. Für viele Anfänger besteht Angular aus *ngIf, *ngFor und dem Einsatz von einer der oben gelisteten Bibliotheken, aus denen dann das erstbeste Element für den jeweiligen Einsatzzweck herausgepickt wird. Daran ist grundsätzlich auch gar nichts schlecht. Schließlich will man ja das Rad nicht neu erfinden, oder?

Immer öfters fragen mich Kollegen allerdings, wie sie diese und jene Funktionalität in Komponente XY aus Bibliothek Z integrieren können. In diesem Tutorial möchte ich anhand eines immer weiter ausgedehnten Beispiels zeigen, wie an die eigenen Bedürfnisse angepasste, wiederverwendbare Komponenten erstellt werden können.

Denn: Manchmal funktioniert es ganz einfach, weil der Autor der Komponente auf Wiederverwendbarkeit geachtet hat, doch bei komplizierten Vorhaben sind die Möglichkeiten einer Standardkomponente irgendwann erschöpft. Die Autoren der Bibliotheken machen einen guten Job, aber ab einer gewissen Komplexität macht es einfach Sinn, nicht mehr auf Standardkomponenten zurückzugreifen, sondern selber wiederverwendbare Komponenten zu erstellen.

 

ngContent

Die wohl einfachste Form einer wiederverwendbaren Komponente besteht darin, Inhalt, der von außen in eine Komponente hereingegeben wird, innerhalb der Komponente darzustellen. Dafür bietet sich ngContent an.

An dieser Stelle führe ich das Beispiel ein. Nehmen wir an, wir möchten einen Onlineshop für Getränke programmieren. Die einzelnen Getränke präsentieren wir in Form von Kacheln. (Der Onlineshop ist sehr geizig, darum wurde auf schönes CSS verzichtet. :P)

Es existiert eine Komponente „Kachel“ (engl. „tile“), welche uns ein Grundgerüst liefert. Per Content-Projektion möchten wir innerhalb der Kachel etwas darstellen, was wir von außen in die Kachelkomponente hineingeben. Das Ergebnis ist folgendes:

Eine simple Kachel mit dem Namen des Produkts, einer Liste an Inhaltsstoffen und einem „Kaufen“ Button. Unten sehen wir den relevanten Teil des dafür geschriebenen Quellcodes. Innerhalb der tile.component.html nutzen wir <ng-content></ng-content>, um das darzustellen, was sich zwischen dem Start- und Endknoten unserer tile Komponente befindet. Also alles, was zwischen <ngc-tile> und </ngc-tile> ist.

Dieser Bereich wird in Angular auch DirectiveContent genannt, daher auch der Name ngContent. 

tile.component.html:

productList.component.html:

 

ngContent mit select

Neben der Möglichkeit den gesamten DirectiveContent zu projizieren, ist ngContent mittels des select Attributes auch in der Lage, bestimmte Elemente zu selektieren, und nur diese anzuzeigen. Anhand eines CSS-Selectors, der dem select mitgegeben werden kann, wird definiert, welches Element projiziert werden soll. Diese Elemente müssen ein direktes Kind-Element der Komponente sein. Da zum Beispiel die Liste mit Inhaltsstoffen kein direktes Kind ist, können wir diese nicht selektieren!

Der nachfolgende Code-Schnipsel projiziert genau dasselbe, wie im Bild oben dargestellt.

 

Lagern wir die tile-actions aus, können wir diese ebenfalls von außen in die Kachel hineingeben und per ngContent darstellen:

productList.component.html:

tile.component.html:

Möchten wir die actions oben und den restlichen Inhalt unten sehen, so reicht es, die beiden ng-content Knoten zu vertauschen.

Somit haben wir eine erste, sehr einfache Komponente in Form einer anpassungsfähigen Kachel geschaffen.

Ich persönlich nutze ngContent allerdings selten bis gar nicht. Es gibt mit ngTemplate und dem ngTemplateOutlet einfach eine zu mächtige Alternative zur Inhaltsprojektion, wie ihr im Weiteren erfahren werdet.

 

ngTemplate

Eine weitere, mächtigere Möglichkeit zur Erstellung von wiederverwendbaren Komponenten ist die Nutzung von ngTemplate und dem ngTemplateOutlet. Ein Template ist eine Schablone, welche definiert wie eine DOM Struktur darzustellen ist. Auch wenn du ngTemplate vielleicht noch nicht aktiv angewendet hast, genutzt hast du es bestimmt schon. Hinter strukturierten Direktiven wie *ngIf oder *ngFor steckt nämlich ein ngTemplate.

Mittels ngTemplateOutlet können wir Inhalt erzeugen, welcher der Definition des Templates entspricht. Durch ngTemplateOutletContext können wir dem Template Objekte zur Verfügung stellen, welche dann innerhalb der <ng-template> Knoten verwendet werden können.

Mit ngTemplate können wir das gleiche Ergebnis erzielen wie oben im Beispiel mit ngContent.

tile.component.html:

productList.component.html:

 

Hier definieren wir ein <ng-template>, weisen es der variablen „tileTemplate“ zu und geben die Referenz zu diesem Template als Input-Binding unter dem Namen tileTemplate an die ngt-tile Komponente. In der TileComponent nutzen wir [ngTemplateOutlet] sowie [ngTemplateOutletContext], um das Template an der gewünschten Stelle einzubinden und das Produkt zur Verfügung zu stellen.

Mittels let-product können wir das $implicit Element aus dem templateContext zugängig machen. Dies ist letztlich nur ein shorthand für let-product=“$implicit“.

Im Grunde erreichen wir so die gleiche Darstellung wie mit ngContent. In diesem einfachen Beispiel ist der ngContent-Ansatz wohl die einfachere und bessere Wahl, denn wir nutzen nicht das volle Potenzial der Templates.

Jedoch kennt in diesem Beispiel die Kachelkomponente das Produkt. Im ersten Beispiel wurde alles von außen per Inhaltsprojektion hineingegeben. Die Kachel wusste dort also gar nicht, was sie darstellt. Da der Kachel das Produkt nun bekannt ist, könnte sie selber ein Template definieren, im Grunde ein Standardtemplate. Ich nutze solche Standardtemplates gerne bei meinen selbst gebauten Komponenten.

So erspare ich mir redundanten Code: Stellen wir uns vor, wir möchten auf diversen Seiten des Shops diese Kacheln anzeigen. Per Inhaltsprojektion, aber auch per ngTemplate, welches wir von außen hineingeben, müsste ich diese HTML-Elemente ständig neu definieren. Da die Darstellung der Kachel jedoch in den meisten Fällen gleich sein wird, können wir in der Kachel selbst ein Standardtemplate definieren, so dass wir in den meisten Fällen kein gesondertes Template mitgeben müssen.

Die Kachel verwendet ihr eigenes Standardtemplate zur Darstellung des Inhalts, und falls wir dennoch eine spezielle Darstellung wünschen, können wir den Aufbau der Kachel von außen mittels ngTemplate beeinflussen. Das nachfolgende Beispiel zeigt einen solchen Aufbau:

Es existiert weiterhin das Input Binding für ein ngTemplate von außerhalb. Falls dieses nicht vorhanden, also undefined oder null, ist, wird das defaultTileTpl verwendet, welches die Kachelkomponente selbst vorgibt.

PS: All diese Beispiele gibt es zum Ansehen und Rumprobieren. (siehe Link am Ende dieses Artikels)

 

Kachel mit sektionsweisen Templates

Bis jetzt können wir die Kachel per Standardtemplate oder per extern hineingegebenem customTemplate verwenden. Dies reicht für viele Anwendungsfälle bereits aus. Einen Schritt weiter können wir gehen, indem wir die Kachel in verschiedene Bereiche unterteilen. Im Falle des nächsten Beispiels habe ich die Bereiche Header, Content, Actions und Overlay gewählt. Wir erstellen also für jeden dieser Bereiche ein Standardtemplate, welches unsere grundsätzlichen Feature-Anforderungen abdecken sollte.

Durch diese Zerteilung der Kachel können wir genauere Anpassungen vornehmen. Wir können unsere Kachel nun im Standardtemplate nutzen:

Oder aber für Sonderangebote mit einem HeaderTemplate:

Oder aber mit angepasstem ActionTemplate:

Oder, wenn wir das OverlayTemplate verwenden möchten, mit einem Hoverstyle:

Es ist in allen Fällen die gleiche Kachel. Nur die Templates sind getauscht.

Nun hält sich der Vorteil bislang in Grenzen, da unsere Kachel bis auf den grauen Rahmen keinerlei Funktionalität bietet. Dies können wir ändern, indem wir die ProductListComponent erweitern.

Nehmen wir an, wir möchten beim Klick auf eine Kachel unterhalb ein Element öffnen, welches zusätzliche Informationen zum Produkt anzeigt. Da wir nun keinen direkten Zugriff auf die Kachel selbst haben, müssen wir die Templates durch die ProductListComponent schleusen und per Input-Binding an die TileComponent übergeben. Das defaultDetailsTemplate soll angezeigt werden, wenn auf eine Kachel geklickt wird.

productList.component.html:

Zum Erzeugen des detailViews verwende ich den ViewContainerRef der letzten Kachel aus der Reihe der geklickten Kacheln. Um an diesen heranzukommen, lassen wir uns den ViewContainerRef ganz einfach per Dependency Injection injecten:

Zusätzlich benötigen wir später noch die Referenz auf die ProductListComponent sowie den ElementRef der KachelKomponente. Das Erzeugen des Views selbst geschieht in der ProductListComponent (Auszug):

Wie man an diesem Schnipsel sieht, arbeite ich auch an dieser Stelle wieder mit der bereits von der Kachel bekannten Mechanik zum Überschreiben eines Templates.

Das Resultat kann sich sehen lassen (wenn man mal vom Styling absieht)

Die aktive Kachel hat nun einen schwarzen Rand und ein Dreieck unterhalb.

Nun könnte es ja sein, dass der Getränke-Shop ebenfalls als Reseller für Mobilfunktarife tätig ist. Da die Kachel wiederverwendbar ist, können wir das detailTemplate überschreiben und so ein Bestellformular integrieren:

 

Wie man an diesem Beispiel sieht, kann mit recht geringem Aufwand ein wunderbares Gerüst für die Angular-Anwendung geschaffen werden. Das hier gezeigte Beispiel stammt übrigens in abgespeckter Form aus einem Kundenprojekt. Dort kommt diese Komponente inzwischen an mindestens zehn verschiedenen Stellen in unterschiedlicher Form zum Einsatz. Ich habe die Erstellung dieser Komponente bislang nicht bereut und ich bin zuversichtlich, dass diese mehrere Jahre halten wird. Ein Faktor, den man bei Drittanbieter-Bibliotheken nie abschätzen kann: Existiert die Bibliothek nächstes Jahr noch? Wird meine verwendete Komponente auch mit dem Release von Angular 8 oder 9 funktionieren?

Baue ich die Komponente selber, weiß ich, dass ich sie auch für Angular 8 oder 9 fit machen kann. Außerdem kann ich jederzeit Features hinzufügen oder verändern, was bei Standardkomponenten nur bedingt funktioniert. In dem besagten Kundenprojekt unterstützt die Produktliste inzwischen auch Paging sowie das Gruppieren von Kacheln. Alles Dinge, die mit einer Komponente von der Stange undenkbar gewesen wären. Also, nehmt euch die Zeit und baut eure eigenen wiederverwendbare Komponenten. Es lohnt sich.

Alle hier gezeigten Beispiele findet ihr zum Download auch auf github.com oder zum Anschauen/Ausprobieren auf stackbliz.com oder hier im Blog als iFrame:


 

Noch mehr spannende Artikel zu Entwicklerthemen und rund um Agilität gibt es einmal pro Quartal in unserem Newsletter. Am besten gleich abonnieren! In unseren Trainings und Workshops der lise Academy geben wir unser gesammeltes Wissen weiter. Etwa in den Seminaren zu Kubernetes, Scrum, Behavior Driven Development und noch einigen mehr.

Mit der lise Academy lernen

 

 

Diesen Artikel weiterempfehlen

 Teilen  Teilen  Teilen  Teilen