Dialog-Modul: Dynamische Komponenten mit Angular erzeugen

Nicht nur Autos können fabrikmäßig hergestellt werden. Photo: Shutterstock

Dialoge steuern die Interaktion zwischen Webseiten und Nutzern. Sie begegnen uns überall im Netz, etwa als Login-Dialog, als Hinweis auf die AGB oder als Fehlermeldung beim Ausfüllen eines Formulars. Also möchte man dem Nutzer Hinweise jeglicher Art anzeigen, ohne dass der Seitenfluss dabei gestört wird.

Oft werden solche Dialoge direkt mit in den HTML-Code der entsprechenden Seite integriert. Das hat den Nachteil, dass auf Webseiten mit vielen Dialogen jeder einzelne neu geschrieben werden muss, was zu einer schlechten Codequalität und einer aufwendigen Wartung führt. Denn auch Änderungen in der Logik oder dem Layout der Dialoge wären nur einzeln durchführbar. Bei komplexen Webportalen bindet das viel Zeit und Ressourcen.

Motivation und Ziel

Warum sollte man eine Komponente dynamisch erstellen?
Dialoge — Auf vielen Webseiten findet man Schaltflächen, nach deren Betätigen sich verschiedene Dialoge öffnen: Hinweise oder um zusätzliche Eingaben vom Benutzer einzuholen. Um das Problem mit redundantem Code vorzubeugen, wäre es eine gute Idee eine “Fabrikmethode” zu schaffen:

  • Man definiert die Komponentenklasse, die den gewünschten Dialog darstellt, und alle Informationen, die sie benötigt.
  • Diese Informationen werden verwendet, um die Komponente “on-the-fly” zu erzeugen.
  • Behalte die Referenz auf die erstellte Komponente, um diese auch wieder zerstören zu können.

Kennengelernt und angewendet habe ich diese Methode bei meiner Arbeit am Angular Projekt “Ausweitung der LKW-Maut auf deutsche Bundesstraßen”. Zur Buchung von Mautstrecken werden jede Menge Dialoge verwendet, zum Beispiel um Login-Details abzufragen, AGB zu bestätigen oder Hilfethemen anzuzeigen.

Vorteile des Dialogmoduls

  • Alle Dialoge liegen gesammelt in einem Modulverzeichnis.
  • Dialoge werden erst dann erzeugt, wenn sie gebraucht werden.
  • Neue Dialoge können mithilfe von Vorlagen einfach und schnell erstellt werden.
  • Das Modul kann als Paket problemlos auch in anderen Angular-Projekten verwendet werden.

Funktionsweise

Informationsfluss im Dialogmodul

Am einem einfachen App-Beispiel verwenden wir drei Schaltflächen zum Anzeigen unterschiedlicher Dialoge:

  • Ein Schaltfläche erzeugt eine schlichte Dialogkomponente.
  • Eine Weitere erzeugt eine “Button & Text” Dialogkomponente.
  • Die Dritte erzeugt eine Dialogkomponente mit einem Eingabefeld.

In der App-Komponente haben wir eine übergeordnete Komponente mit dem Selektor “#dialogContainer”. Diese fungiert als ein Container für die zu kreierende Dialogkomponente, nimmt als Eigenschaft die Komponenten-Informationen entgegen und gibt eine Referenz der zu erzeugenden Dialogkomponente.

Klickt man auf eine der Schaltflächen, wird eine Aktion mit eindeutigen Daten in die Container-Komponente gesendet. Der datengetriebene Container generiert anhand der Informationen die gewünschte Dialogkomponente.

Dynamische Komponentenerstellung im Dialogmodul

Hier findet die eingangs erwähnte Fabrikmethode Anwendung. Das Entwurfsmuster beschreibt, wie ein Objekt durch Aufruf einer Methode erzeugt wird anstatt durch direkten Aufruf eines Konstruktors. Dazu brauchen wir nur wenige Dinge:

  • Wissen über alle Dialogkomponenten, welche dynamisch erzeugt werden können
  • Zugriff auf den “ComponentFactoryResolver-Service“ von Angular, um die Komponenten-Fabrikmethode zu verwenden
  • ein Injector-Objekt, um die übermittelten Daten als Konstruktions-Informationen für die Dialogkomponenten zu injizieren (“Dependency Injection”)
  • eine Variable zum Speichern der erzeugten Dialogkomponente, damit diese auch wieder zerstört werden können

Das Wissen, welche Dialogkomponenten die Container-Komponente erzeugen kann, wird über die “entryComponents” Eigenschaft in den Metadaten des Dialogmoduls registriert.

Der im Fluss-Diagramm (oben) dargestellte Dialogservice dient als Vermittler zwischen Aufrufer und Dialog-Container und erstellt dazu einen Ereignisbus. Es existiert ein Ereigniskanal für das beschriebene Emittieren eines Dialogs und ein rückläufiger Kanal für das Auswerten einer Antwort aus dem Dialog.

Diese zugegebenermaßen etwas grobe Beschreibung der Funktionsweise, ist der Kern des dynamischen Erstellen von Angular-Komponenten. Wer an näheren Details interessiert ist, findet diese direkt im Code der Container-Komponente auf Github.

Fazit

Mit diesem Setup ist es möglich, dynamische Angular-Komponenten “on-the-fly” zu erzeugen. Der Dialog-Container wird nur dann angesteuert bzw. mit Inhalt gefüllt, wenn auf der Webseite ein Dialog erscheinen soll. Wir verwalten die Dialogkomponenten in einem Angular-Modul und halten expliziten Dialog-Code aus dem fachlichen Bereich fern.

Ausblick & Optimierung

Die Dialogkomponenten verwenden im Konstruktor den “Angular-Injector” get() Aufruf, um zur Laufzeit die übermittelten Konstruktions-Informationen setzen zu können. Mit Angular ab Version 4 ist der “Injector StringToken” überholt. Ich nehme an, ab Angular > 5 muss man auf “ InjectionToken” umstellen: https://angular.io/guide/dependency-injection (siehe Abschnitt “Dependency injection tokens”).

Das Dialogmodul ist auch noch nicht “npm install” bereit. Hier sollte noch herausgefunden werden, welche Schritte nötig sind, um es über den JavaScript “Package Manager” verwenden zu können.

Wer eine Idee oder weitere Anmerkungen hat, kann hier oder im Github-Projekt sehr gerne kommentieren!

Links