Dynamische Dashboards mit Angular Elements

Mit Angular Elements lassen sich herkömmliche Angular-Komponenten als Web Components bereitstellen. Sie sind nicht nur Framework-unabhängig, sondern lassen sich auch dynamisch in den Browser laden.

Know-how  –  3 Kommentare
Dynamische Dashboards mit Angular Elements

Angular Elements erlauben das Bereitstellen Framework-unabhängiger Komponenten. Dahinter verbirgt sich die Idee von Web Components, die der Browser genau wie andere HTML-Elemente behandelt und die somit Framework-übergreifend nutzbar sind. Ganz nebenbei erhält man noch einen weiteren Vorteil: Web Components lassen sich äußerst einfach dynamisch laden und zur Seite hinzufügen. Dazu sind lediglich ein paar einfache DOM-Manipulationen notwendig. Diesen Aspekt greift dieser Artikel auf, um zu zeigen, wie sich ein dynamisches Dashboard mit Angular Elements erzeugen lässt, zu dem der Benutzer beliebige Kacheln hinzufügen kann.

Abb. 1: Dynamisches Dashboard mit Angular Elements

Das Beispiel verwendet drei Arten von Kacheln: Die Build-in-Kachel wird gemeinsam mit der Angular-Anwendung kompiliert. Das ist auch bei der Kachel mit dem Titel "Lazy" der Fall. Allerdings lagert des Command-Line Interface (CLI) sie in ein eigenes Bundle aus, das die Anwendung bei Bedarf in den Browser lädt. Das klappt mit einem neuen Feature des CLI, das Lazy Loading ohne Router ermöglicht. Die Kachel mit der Beschriftung "External" kompiliert man getrennt und lädt sie dann bei Bedarf in den Browser. Obwohl sie auch mit Angular geschrieben ist, könnte sie auch jedes andere Framework, das Web Components unterstützt, erstellen. Der gesamte Quellcode ist auf GitHub zu finden.

Als Kachel nutzt das Beispiel Komponenten, die lediglich drei numerische Werte entgegennehmen und diese präsentieren:

@Component({
// selector: 'app-dashboard-tile',
templateUrl: './dashboard-tile.component.html',
styleUrls: ['./dashboard-tile.component.css']
})
export class DashboardTileComponent {
@Input() a: number;
@Input() b: number;
@Input() c: number;
}

Der Selektor kommt hier ganz bewusst nicht zum Einsatz, da eine Web Component anderweitig einen Tag-Namen erhält. Dadurch vermeidet man Namenskonflikte. Das in Version 6 eingeführte Paket @angular/elements kann diese Angular-Komponente als Web Component bereitstellen:

npm i @angular/elements --save

Im Zusammenhang mit diesem Paket müsste von Custom Elements die Rede sein. Dabei handelt es sich um den Standard für Web Components, der das Definieren eigener HTML-Elemente erlaubt. Damit das Custom Element auch in jedem Browser läuft, ist noch ein Polyfill zu laden. Das nachfolgende unterstützt auch den Internet Explorer 11:

npm i @webcomponents/custom-elements --save

Um ältere Browser hochzurüsten, ist das Polyfill in die Anwendung zu importieren. Das kann zum Beispiel am Ende des durch das CLI eingerichteten polyfills.ts erfolgen:

import '@webcomponents/custom-elements/custom-elements.min';

Paradoxerweise ist auch ein Polyfill für Browser, die mit Custom Elements umgehen können, notwendig. Das liegt daran, dass Custom Elements für ECMAScript 2015 und neuer definiert sind, während heutzutage zur Unterstützung älterer Browser häufig das alte ECMAScript 5 als Kompilierungsziel zum Einsatz kommt. Zur Vermittlung zwischen diesen beiden Welten enthält das vorhin erwähnte Paket ein über angular.json eingebundenes Skript:

"scripts": [
"node_modules/@webcomponents/custom-elements/src/native-shim.js"
]

Das Registrieren in angular.json stellt sicher, dass das Skript in einem separaten Bundle landet. Das ist leider notwendig, da sich die Polyfills nicht in jedem Browser miteinander vertragen.

Jetzt kann man die Angular-Komponente als Custom Element bereitstellen. Dazu ist sie wie gewohnt zu deklarieren und zusätzlich als Entry Component festzulegen, da Angular Elements sie zur Laufzeit dynamisch erzeugt. Danach wandelt das Modul die Komponente mit der Funktion createCustomElement aus dem Paket @angular/elements in ein Custom Element um:

@NgModule({
[…],
declarations: [
[…]
DashboardTileComponent
],
entryComponents: [
DashboardTileComponent
]
})
export class DashboardModule {

constructor(private injector: Injector) {

const tileCE = createCustomElement(DashboardTileComponent, { injector: this.injector });
customElements.define('dashboard-tile', tileCE);

}

}

Der Aufruf von createCustomElement kann überall in der Angular-Anwendung erfolgen. Um jedoch auch die Registrierung von Custom Elements im jeweiligen Modul zu kapseln, kommt hier jedoch der Modul-Konstruktor zum Einsatz. Um das Custom Element am Dependency-Injection-Mechanismus von Angular anzuschließen, erhält createCustomElement auch den aktuellen Injector. Der Aufruf der Methode customElements.define hat nichts mehr mit Angular Elements zu tun: Sie stammt aus dem Custom-Elements-Standard und registriert eine Komponente für einen bestimmten Tag-Namen. Die Anwendung kann die Kachel nun überall aufrufen, indem sie diesen Tag-Namen verwendet. Da sich der Browser um die Darstellung kümmert, funktioniert dies in jedem Framework und sogar mit blankem JavaScript:

<dashboard-tile a="100" b="50" c="25"></dashboard-tile>

Der Angular-Compiler ist allerdings verwirrt, da er den Namen dashboard-tile nicht kennt. Damit das nicht in einem Fehler mündet, ist das CUSTOM_ELEMENTS_SCHEMA in die betroffenen Module aufzunehmen:

@NgModule({
[…]
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class AppModule {
}