12. Feb 2021
Lesedauer 31 Min.
Einbinden der Angular-Plattform
Cross-Plattform-Development mit Webtechnologien (Teil 3)
Ionic integriert für die Cross-Plattform-Entwicklung die populäre Angular-Plattform und bezieht diese in die eigene Software-Entwicklung mit ein.

Als Drifty mit der Entwicklung von Ionic im Jahr 2012 startete, wurde das Framework auf Webtechnologien mit dem Schwerpunkt Integration ausgerichtet. Zielsetzung bildete stets die Cross-Plattform-Entwicklung für Android, iOS und Progressive Web Apps (PWAs) mit den Webtechnologien HTML/CCS und JavaScript. Mit der Version 2 im Jahr 2017 stellten die Entwickler von Ionic neben der Native-Version auch ihre Integrationstechnologie für Angular bereit. Seitdem rückt die Multi-Framework-Kompatibilität von Ionic immer mehr ins Zentrum: Mit Version 4 im Jahr 2019 unterstützt Ionic neben Angular auch React und ganz aktuell auch Vue.js.Angular (manchmal auch kurz als Angular 2+ bezeichnet) stellt ein Framework für Web-Apps dar – es gilt als Nachfolger von AngularJS. Ein Team bei Google entwickelte AngularJS und programmierte mit den damit gesammelten Erfahrungen das Angular-Framework von Grund auf neu. Als Angular 2+ im Sommer 2016 von Google als Open-Source freigegeben wurde, verbreitete sich das Webframework aufgrund seiner Features recht schnell. Angular als Framework vereinfacht die Entwicklung dynamischer Web-Apps durch Single Page Applications (SPAs). Eine derartige Einzelseiten-Anwendung lädt beim Programmstart die gesamte App, die aus einem einzigen HTML-Dokument besteht – daher das Attribut Single.
Single Page Applications
Dieses Merkmal stellt der wesentliche Unterschied einer SPA zu einer herkömmlichen Web-App dar, die sich aus mehreren untereinander verknüpften HTML-Dokumenten zusammensetzt. Dabei lädt die Web-App bedarfsorientiert die jeweilige HTML-Seite vom Webserver. Aus Sicht einer Client-Server-Architektur würde man eine SPA als Rich-Client- beziehungsweise Fat-Client-Anwendung bezeichnen.Zur Laufzeit setzt sich eine SPA vollständig dynamisch auf dem Client im Webbrowser zusammen. Somit entfallen die Wartezeiten am Client für die Übertragung weiterer HTML-Dokumente vom Webserver, was sich positiv auf die Performance auswirkt. Es entsteht lediglich ein Timelag für die Aktualisierung der Daten.Im Unterschied zu anderen Frameworks wie jQuery oder React, die lediglich das User-Interface abdecken, handelt es sich bei Angular um ein Framework für die komplette Programmierung von Web-Apps.Mit den Features von Angular lassen sich alle Anforderungen umsetzen, die am Client der App auftreten. Selbst die Datenhaltung kann im mithilfe der Web-Storage-Funktion am Client erfolgen. Im Offline-Fall, der den Datenbankserver nicht erreicht, greift der Client auf einen Zwischenspeicher zurück. Seitens der Anwendungsarchitektur deckt Angular damit vollständig die Client-Seite ab – das Framework unterstützt eine Trennung von client- und serverseitiger Logik.Angular erlaubt es, sowohl ein MVC- (Model-View-Controller)- als auch eine MVVM- (Model-View-ViewModel)-Architektur umzusetzen. Als eine Variante des MVC-Musters trennt das MVVM-Entwurfsmuster die Präsentation von der Logik der Benutzungsschnittstelle. Das MVVM-Konzept basiert auf der Datenbindung (Data-Binding) die View enthält das User-Interface, in der Mitte befindet sich das VM (ViewModel) dieses kommuniziert mit dem Model, das die Geschäftslogik und Daten umfasst (Bild 1). Die View zeigt alle Elemente der Benutzeroberfläche als HTML-Dokument an.
Entwurfsmuster MVVM:Data-Binding trennt die Darstellung/Präsentation (View) von der User-Interface/Präsentations-Logik (ModelView) und der Datenzugriffsschicht (Model)(Bild 1)
Simon
Um Inhalte darzustellen und Eingaben der Benutzer weiterzuleiten, bindet sich die View an das ViewModel (VM). Dazu benötigt man einen Datenbindungsmechanismus, der die View mit dem ViewModel verknüpft. Das VM realisiert das Modell der View und enthält neben einer Schnittstelle zum User-Interface (Präsentation) auch die User-Interface-Logik (Präsentations-Logik) mit den Plausibilitäten; es dient als Bindeglied zwischen View und dem Modell. Im Unterschied zum Modell, das es bezogen auf die Anwendungslandschaft nur einmalig gibt, enthält eine App mehrere verschiedene Ausprägungen von Views und ViewModels.
Grundkonzepte des Angular-Frameworks
Das VM nutzt Methoden und Dienste des Models und reicht deren Ergebnisse durch den Datenbindungsmechanismus an die View weiter. Das Modell stellt die Datenzugriffsschicht dar, es liefert die Inhalte die dem Benutzer angezeigt und von ihm bearbeitet werden. Auch die letztmalige, das heißt endgültige und damit globale Validierung der Daten findet im Model statt. Seitens der Realisierung befasst sich Angular mit der View und dem ViewModel oder im Falle des MVC-Patterns mit der View und dem Controller. In beiden Fällen erfolgt eine Umsetzung dieser Bestandteile in Angular mit dem Konzept der Komponentenorientierung. Wobei sich eine Komponente aus den elementaren Bauteilen von Angular zusammensetzt.HTML-Seite mit der Angular-App in src/index.html
Die Angular-App residiert auf der HTML-Startseite index.html, die sich im src-Unterverzeichnis des Projektordners befindet. Um in der Angular-App HTML-Tags mit einem eigenen Präfix zu verwenden, definiert man dieses bei der Neuanlage der Angular-App über den Optionsparameter --prefix mytag. Der CLI-Befehl ng new laden --prefix mytag erzeugt eine App, die anstatt des standardmäßigen <app>-Präfix im Tag den Eintrag <mytag> besitzt. Danach heißt für eine Komponente mit den Namen buch der Tag jetzt <mytag-buch>.
Angular besitzt einen sehr großen Funktionsumfang, der mit verschiedenen Konzepten über spezielle Konstrukte realisiert wurde. Aus der hohen Mächtigkeit von Angular folgt eine entsprechende Komplexität, woraus eine flache und länger andauernde Lernkurve resultiert. Der Lernende benötigt mehr Beispiele, viele Übungen und selbständige Versuche, bis er beginnt seine eigene produktive Leistung zu verbessern. Insofern fällt einem Anfänger der Einstieg in die Programmierung mit Angular nicht leicht. Aufgrund der großen Mächtigkeit sprechen manche Entwickler davon, dass Angular sich von einem Framework zu einer kompletten Programmierplattform weiterentwickelt hat. Insbesondere da Angular neben dem reinen API schon immer auch Werkzeuge und Generatoren beinhaltet.
Templates stehen im Mittelpunkt
Im Mittelpunkt der Konzepte von Angular stehen Templates, die global betrachtet das Design einer Web-App hauptsächlich mit HTML definieren. Directiven innerhalb der Templates erweitern die vorhandenen HTML-Tags. Um Elemente der View (des User-Interface) mit dem Datenmodell zu verbinden, kommt der Mechanismus des Data-Binding zum Einsatz. Dieser Mechanismus muss nur definiert werden, seine Durchführung übernimmt die Angular-Plattform automatisch. Erfolgt Data-Bindung in zwei Richtungen (Two-way data binding), so ändern sich die Daten der View nur, wenn das mit der View verbundene Modell sich ebenfalls ändert. Es findet eine Synchronisation der Daten zwischen View und Modell statt.Für die Definition von Ausgaben innerhalb eines HTML-Bereichs kommen Expressions zum Einsatz; diese führen zu leichter lesbarem HTML-Code. Mittels Services nimmt Angular globale Wandlungen vor (zum Beispiel für die Templates und Directiven) oder realisiert zusätzliche Funktionalitäten wie http(s)-Verbindungen.Über integrierte Filter (in der Angular-Terminologie werden sie Pipes genannt) durchsucht Angular anhand spezifizierter Kriterien Datenlisten. Das Entwurfsmuster des Dependency-Injection gibt anwendungsseitig die Steuerung der Ausführung an das Framework ab. Dieses Konzept bezeichnet man auch als Inversion of Control (IoC), deutsch: Umkehrung der Steuerung.Angular enthält ein CLI (Command Line Interface), um Projekte anzulegen und deren Inhalte zu verwalten. Für die Installation des CLI von Angular benötigt man den Package-Manager npm von Node.js (siehe Teil 2). Der Befehl npm install -g @angular/cli in einem Terminalfenster (einer Eingabeaufforderung) installiert das Angular CLI als globales Modul. Jeder Befehl des Angular-CLI beginnt mit den beiden Buchstaben ng; so zeigt das Kommando ng version Informationen zum installierten Angular-System an. Gibt man lediglich den Befehl ng ohne weiteres Kommando oder ng help ein, so erscheinen alle verfügbaren Befehle des Angular CLI am Bildschirm. In der Regel lassen sich CLI-Befehle nur im Ordner eines Angular-Projekts ausführen.Einstieg in die Programmierung
Der Befehl ng new myapp erzeugt einen Ordner myapp und legt darin eine neue Anwendung ab. Bei der Neuanlage des Projekts stellt Angular einige optionale Fragen (Bild 2), die man einfach mit der Return-Taste beantworten kann – in diesem Fall übernimmt das CLI die in eckigen Klammern [] und Großbuchstaben stehende Vorgaben als Antwort. Will man eine andere Auswahloption übernehmen, dann tippt man den in eckigen Klammern genannten Buchstaben ein. Erscheinen mehrere Optionen in einer Liste und in der ersten Zeile das Größer-Zeichen >, so kann man über die nachunten gerichtete Pfeiltaste einen der Vorgabewerte aus den nachfolgenden Zeilen durch Drücken der Return-Taste festlegen.
Angular stelltdem Entwickler eine Kommando-Oberfläche mit Werkzeugen und Generatoren bereit(Bild 2)
Simon
Anschließend erzeugt das CLI alle erforderlichen Unterverzeichnisse mit den entsprechenden Entwicklungsdateien und einen neuen Workspace. Unter einem Workspace versteht man eine Sammlung von Angular-Projekten (Anwendungen und Bibliotheken), die sich alle unterhalb eines Verzeichnisses – der Workspace-Root befinden.Die Definitionen für die Konfiguration eines Workspace befindet sich in der Datei angular.json; sie enthält alle Standardvorgaben und projektspezifischen Einstellungen zu den externe Ressourcen sowie den Werkzeugen für Entwicklung und Build. Die drei weiteren .json-Dateien beschreiben die Konfigurationen für den npm-Package-Manager (package.json), für den TypeScript-Transpiler (tsconfig.json) und für den TypeScript-Linter (tslint.json).
Ausführung der Web-App
Für die Ausführung einer Web-App muss man sich in einem Befehlsfenster in ihrem Ordner befinden. Der Befehl ng serve --open übersetzt die TypeScript-Dateien, führt einen Build der Angular-App durch, startet eine Node.js-Webserver und zeigt die App im Default-Browser an. Dabei läuft der Webserver (in der Angular-Terminologie Angular Live Development Server genannt) als localhost auf dem Port 4200. Der Parameter --port 3000 beim ng serve-Befehl vergibt eine andere Portnummer, hier: 3000. Die Tastenkombination Strg+C beendet den im Hintergrund laufenden Webserver. Speichert man im Quellcode durchgeführte Änderungen, so aktualisiert der Webserver die Anwendung automatisch im Browser.Jede Angular-App besitzt in ihrem Projekt-Ordner einen einheitlichen Aufbau mit folgenden Unterverzeichnissen:Das Unterverzeichnis app des src-Ordners enthält den spezifischen Quellcode einer Angular-App mit dem man als Entwickler hauptsächlich arbeitet. Dort findet man das HTML-Template, die zugehörige CSS-Datei, die TypeScript-Root-Komponente und das TypeScript-Root-Modul (Bild 3).
Der app-Ordner enthältalle anwendungsspezifischen Dateien für die Programmierung mit Angular(Bild 3)
Simon
Die im src-Ordner abgelegte index.html-Datei entspricht dem Entry-Point der App; die styles.css-Datei enthält die globalen CSS-Vorgaben mit den Gestaltungsanweisungen.
Core-Bestandteile der Angular-Plattform
Das HTML-Template app.component.html besteht ausschließlich aus HTML-Anweisungen ohne JavaScript- oder TypeScript-Code. Das HTML-Template nimmt die Platzhalter für die TypeScript-Properties auf, zum Beispiel {{ title }}. Die Definitionen zu den Platzhaltern befinden sich in der app.component.ts-Datei – der Root-Komponente. Das Root-Modul app.module.ts deklariert alle verwendeten Module und die Schnittstellen der Web-App.
Die Core-Bestandteile von Angularliefern dem Entwickler ein Meta-Modell – auf dessen Basis er mit TypeScript die gewünschte Funktionalität der App programmiert(Bild 4)
Simon
Die Angular-Plattform besteht aus vielen unterschiedlichen Bestandteilen, die zusammen betrachtet, das gesamte Spektrum der Funktionalität eines Clients am Frontend einer Web-App abdecken. Als die wichtigsten Core-Bestandteile von Angular gelten: Komponenten, Module mit Modulsystem und Services (Bild 4). Komponenten, Module und Services gelten als die primären Bestandteile von Angular. Diese dienen im Wesentlichen zur Strukturierung einer Angular-Anwendung:Angular selbst besitzt viele vordefinierte Services, die als eigenständiges Modul eine gewisse, logisch zusammengehörende Funktionalität kapseln – beispielsweise für die Kommunikation mit dem Backend, für Animationen oder das Formular-Handling. Ein selbst programmierter Service stellt eine Funktionssammlung zur Wiederverwendung von Quellcode dar, diesen Service erweitert man mittels @Injectable-Decorator. Eine Komponente hat Zugriff auf jeden selbstprogrammierten Service; die Bereitstellung eines Service erfolgt über die Registrierung in einem Modul – beim Erzeugen der Service-Instanz werden die Konstruktor-Parameter injiziert.Die Templates einer Komponente besitzen in der Regel ein dynamisches Verhalten. Sobald die Angular-Plattform diese zur Anzeige rendert, erzeugt diese das DOM (Document Objekt Model) (siehe Teil 1) entsprechend den durch Directiven vorgegebenen Anweisungen. Angular kennt drei verschiedene Typen von Directiven: Komponenten-Directiven findet man beim HTML-Template. Structural Directiven verändern das Layout des DOM, indem sie Elemente hinzufügen oder entfernen.Als Beispiele kann man *ngFor oder *ngIf nennen. Attribute Directive verändern das Aussehen oder Verhalten eines Elements, Komponente oder einer anderen Directive. Typische Beispiele einer Attribut Directive stellt ngStyle oder [ngClass] dar. Die Deklaration einer Directive erfolgt wie die einer Komponente immer innerhalb eines Angular-Moduls über den @Directive-Decorator.Die grundlegenden Bausteine einer Angular-App stellen Components (Komponenten) dar, die jeweils eine bestimmte Aufgabe erfüllen. Beispielsweise lässt sich eine Komponente einem Ausschnitt der Benutzeroberfläche (einer View) der Angular-App zuordnen.Jede Komponente besteht aus drei verschiedenen Dateitypen: einer HTML-, einer CSS- und einer TypeScript-Datei. Der Befehl ng generate component sidebar im Projektordner erzeugt eine Komponente mit dem Namen sidebar und legt alle zugehörigen Dateien in einem eigenen Unterordner mit dem Namen der Komponente (sidebar) an.
Komponenten als Grundbausteine
In der HTML-Datei (dem HTML-Template) befinden sich die HTML-Anweisungen, welche die Struktur der Ausgabe der Komponente definiert, beispielsweise die von der Angular-CLI erzeugte <p>-Tags für einen Absatz. In die TypeScript-Datei mit der .ts-Endung übernimmt man die Geschäftslogik der Komponente und zusätzlich definiert man dort deren Metadaten. Die Metadaten einer Komponente bestehen aus drei Teilen: einer import-Anweisung, einem @Component-Decorator und einer export class-Anweisung. Über die import-Anweisung gibt man alle Module an, deren Services man für die neue Komponente benötigt. Der @Component-Decorator definiert verschiedene Eigenschaften der Komponente. Die zentralen Eigenschaften einer Komponente legt das vorgefertigte Konstrukt @Component({…}) fest (Bild 5). Dort befinden sich für jede Komponente ein selector-Feld, die templateUrl mit dem Ablageort für das HTML-Template und eine styleUrls mit dem Ablageort der zugehörigen CSS- oder Scss-Datei. Anstatt templateUrl und styleUrls kann dort auch eine template- und eine styles- Eigenschaft (Achtung: Plural!) stehen.
Eine Angular-Komponenteerhält ihre Eigenschaften (Properties) wie selector, templateUrl und styleUrls über den @Component-Decorator(Bild 5)
Simon
Von diesen drei Feldern ist lediglich die templateUrl zwingend, die beiden anderen sind optional. Sollte der im HTML anzuzeigende Inhalt klein sein, so codiert man diesen direkt in der template-Eigenschaft. Entweder über eine einfache Zeichenkette in einzelnen (‚…‘) oder in doppelten Anführungszeiten („…“). Über das Accent grave-Zeichen (`…`) kann man eine Zeichenkette auch über mehrere Zeilen im template-Feld aufteilen.
Komponenten auf eine HTML-Seite laden
Über das selector-Feld kann man ähnlich wie bei einer CSS-Anweisung die Komponente im HTML-Quellcode ansprechen und wiederverwenden. Besitzt das selector-Feld den Wert ‚app-element‘, so spricht man das Feld direkt über den Wert in eckigen Klammern an: <app-element></app-element>. Der Wert des selector-Felds muss innerhalb der Angular-App eindeutig sein.Den eindeutigen Namen verwendet man, um die Komponente innerhalb des HTML-DOMs zu identifizieren, sobald dieser im Webbrowser aufgebaut wurde.Das selector-Feld einer Komponente kann verschiedene Typen von HTML-Syntaxelementen (siehe Teil 1) annehmen. Beim zuvor beschriebenen Selector handelt es sich um einen HTML-Element-Namen, in diesem Fall repräsentiert die Komponente das vollständige Element. Der Selector einer Komponente kann auch ein Attribut eines HTML-Elements darstellen. In diesem Fall findet man eckige Klammern bei der Definition des selector-Felds: selector: ‚[app-element]‘.In diesem Fall greift man in der HTML-Datei der Komponente auf den Selector wie auf ein Attribut und nicht wie auf ein HTML-Element zu: <div app-element> </div>, dabei dient das div-Tag als Element zusammen mit dem Selector als Attribut. Zum Schluss fungiert der Wert des selector-Felds über den Punkt (.)-Operator auch als Klasse: selector: ‘.app-element‘. Man definiert den Selektor-Namen als Klasse (<div class= “app-element“> <div>) und das zugehörige Element wird im DOM als Klasse (angelehnt an die Vorgehensweise einer CSS-Klasse) aufgenommen.Mit Pipes Daten aufbereiten
Den Begriff einer Pipe kennt man vielleicht aus der Unix-Welt – dort leitet ein Pipe die Ausgabedaten eines Prozesses als Eingabe an einen anderen weiter. Ähnlich arbeiten Pipes (manchmal auch Filter genannt) in Angular: Eine Pipe reicht Daten direkt im Template an eine Funktion weiter, diese bereiten die Daten zum Beispiel für die Darstellung auf. Pipes kommen häufig zum Einsatz, wenn die im Template darzustellenden Daten nicht im gewünschten Format vorliegen. Pipes transformieren Daten direkt bei der Einbindung ins Template und bringen sie so in das richtige Anzeigeformat.Die Angular-Plattform enthält eine ganze Reihe eingebauter Pipes; zusätzlich kann man als Entwickler auch eigene Pipes implementieren. Ein Pipe lässt sich überall im HTML-Template verwenden, wo ein Ausdruck eingesetzt wird: bei der String-Interpolation, in Property-Bindings und auch in Ausdrücken zusammen mit Directiven. Den Beginn einer Pipe leitet das Pipe-Symbol | ein. Einer Pipe kann man Argumente als Parameter übergeben. Eine Verkettung durch Weiterreichen der Daten von einer Pipe über das |-Symbol an eine andere führt mehrere Pipes hintereinander aus.Einsatzgebiete von Pipes
Das wohl häufigste Einsatzgebiet von Pipes stellt die Internationalisierung dar. Eine Anwendung, die in unterschiedlichen Sprachräumen zum Einsatz kommt, muss je nach Land und Region andere Konventionen für die Darstellung des Datums, der Zahlen- oder Währungsformate berücksichtigen. Für grundlegende Aufgaben besitzt Angular eine ganze Reihe vordefinierter Pipes, deren Namen man direkt im Template einsetzt. Zum Beispiel überführt die Angabe von {{ heute | date: ‘fullDate’ }} im Template die Variable heute das aktuelle Tagesdatum einer Komponente in ein FullDate-Format.Derart vordefinierte Pipes transformieren Zeichenketten in Groß- und Kleinbuchstaben (uppercase, lowercase), formatieren Datums- und Zeitangaben (date), bereiten Dezimalzahlen, Prozent- oder Währungsangaben für die Darstellung auf (number, percent, currency). Für die Darstellung von Datumsangaben kommen Platzhalter für das Jahr (y), für den Monat (m) für den Tag (d) zum Einsatz. Beispielsweise bedeutet die Formatkennung yyyy/mm/dd, dass bei einem Tagesdatum zuerst eine vierstellige Jahreszahl steht, gefolgt von einer zweistelligen Angabe zum Monat und Tag. Die Programmierung eigener Pipes erfolgt über einen @Pipe-Decorator in einer TypeScript-Klasse.
Die Definition eines Angular-Modulserfolgt über den @NgModule-Decorator – dieser definiert die benötigten Metadaten eines Moduls(Bild 6)
Simon
Ein Modul in Angular stellt eine TypeScript-Klasse mit einem @NgModule-Decorator dar. Jede Angular-App besitzt mindestens eine Klasse mit einem @NgModule-Decorator – dieses Modul nennt man Root-Modul; gemäß Konvention erhält es die Bezeichnung AppModule. Angular-Module sollte man nicht mit ES2015- oder TypeScript-Modulen verwechseln. Ein Angular-Modul dient als Mechanismus, Komponenten zu bündeln, Abhängigkeiten und Sichtbarbarkeit zu definieren. Wie jede TypeScript-Klasse (siehe Teil 1) besitzt auch ein Angular-Modul verschiedene Konfigurationsattribute, diese werden über @NgModule definiert (Bild 6):Der Name einer Modul-Datei besteht aus der Bezeichnung der Komponente als Präfix und dem Zusatz module. Heißt die Komponente app, so sollte die zugehörige Modul-Datei mit dem TypeScript-Code den Namen app.module.ts besitzen. Sie befindet sich im Projektordner im gleichen Verzeichnis wie die zugehörige Komponente und nicht im Unterverzeichnis node_modules. Das node_modules-Unterverzeichnis gehört nicht zum Ordner mit dem Quellcode der App; dort findet man alle Abhängigkeiten zu externen Bibliotheken aber keinerlei Beziehungen zum Quellcode der App. Das CLI-Kommando ng generate module besitzt verschiedene Optionen, die der Befehl ng generate module --help ausführlich anzeigt. Die Ablage eines neu erzeugten Moduls erfolgt immer in einem Unterverzeichnis, das den Modul-Namen erhält. So erzeugt ng generate module buch ein Modul mit dem Dateinamen buch.module.ts im Unterverzeichnis buch des app-Ordners.
Code mittels Feature-Modules organisieren
Große Angular-Anwendungen sollte man in verschiedene Module zerlegen, um sie leichter warten und einfacher verwalten zu können. Unterlässt man eine derartige Zerlegung, so wächst der Wartungs- und Verwaltungsaufwand mit zunehmender Größe der App stetig an.Für die Zerlegung der App in einzelne Module existieren unterschiedliche Kriterien. Beispielsweise bietet sich eine Zerlegung nach Funktionsgruppen, Datenklassen, Entwicklungstätigkeiten (Programmierung, Test, Betrieb) und ähnliches an. Als weiterer wichtiger Vorteil der Modularisierung in Angular ergibt sich eine bessere Performance der App: Nachdem eine Anwendung in mehrere Module zerlegt ist, importiert man deren Referenzen in das Root-Module – für die beim Programmstart Angular das Bootstrapping bedarfsorientiert durchführt.Module, die kein Root-Modul einer App darstellen, heißen in der Angular-Terminologie Feature-Modul. Dabei handelt es sich um eine eigenständige TypeScript-Klasse, die über einen @NgModule-Decorator und zusätzliche Metadaten verfügt, wie man sie über das CLI-Kommando ng generate module erzeugt. Ein Feature-Modul übernimmt eine bestimmte Aufgabe innerhalb der App, somit helfen sie die Verantwortlichkeiten der App in einzelne kleinere Bereiche zu zerlegen. Einem Entwickler fällt es wesentlich leichter, sich auf diesen kleineren Zuständigkeitsbereich zu konzentrieren. Um eine größere zusammenhängende Aufgabe der App zu erledigen, arbeiten mehrere Feature-Module mit anderen oder mit dem Root-Modul zusammen.Ladezeit der App
Feature-Module verbessern auch die Performance (das Laufzeitverhalten) der App. Um die Ladezeit der App über das Bootstrapping zu minimieren, kann man Feature-Module auf Bedarf laden, wenn die App sie tatsächlich benötigt (Lazy-Loading). Lazy-Loading erfolgt über das dynamische Import-Konzept von Angular.Dies verkleinert die Größe der App, da zum Programmstart nur die benötigten Module in den Webbrowser am Client geladen werden. Alle anderen Module werden heruntergeladen und dynamisch in die App übernommen. Ferner kann Angular Feature-Module, bevor sie die App anfordert, auch im Voraus laden (Pre-Fetching/Loading). Beide Programmiertechniken verbessern wesentlich die Akzeptanz der App beim Endbenutzer.Directiven in Angular nehmen Manipulationen in DOM vor. Eine Directive ändert das Aussehen, das Verhalten oder das Layout eines DOM-Elements. Somit erweitert eine Directive die HTML-Syntax. Im Wesentlichen handelt es sich bei einer Directive um eine Funktion, die vom Angular-Compiler ausgeführt wird, sobald diese im DOM gefunden wird. Die neu für HTML hinzugefügte Syntax erweitert quasi die Funktionalität der regulären HTML-Anweisungen. Die Angular-Plattform kennt drei verschiedene Klassen von Directiven, die auf deren Verhalten basieren: Component-, Attribut- und Struktur-Directiven.Mit Directiven das DOM manipulieren
Component-Directiven kommen in der TypeScript-Klasse zum Einsatz, sie beschreiben im Detail wie eine Komponente verarbeitet, instanziiert und zur Laufzeit verwendet wird. Eine Komponente stellt also eine Directive mit einem Template dar, diese sind direkt mit einer View der Benutzeroberfläche verknüpft. Eine Attribut-Directive verändert das Aussehen oder das Verhalten eines HTML-Elements einer Komponente oder einer anderen Directive. Dazu zählen alle Built-in-Attribut-Directiven beispielsweise ngClass-, ngModel- oder ngStyle-Directive.Während ngClass eine oder mehrere CSS-Klassen aus einem HTML-Element entfernt oder eine neue hinzufügt, benutzt man ngStyle dazu, dynamisch den Style einer oder mehrerer HTML-Elements zu ändern. Mit ngModel lässt sich Two-Way-Data-Binding mit einem HTML-Formular-Element verknüpfen. Die Directive [(ngModel)] reicht Daten vom TypeScript-Objekt zur Benutzeroberfläche (HTML-Template) und zurück. Struktur- oder Structural-Directiven beginnen mit einem *-Zeichen; sie manipulieren und verändern die Struktur von DOM-Elementen.Typische Beispiele sind die *ngIf-, *ngSwitch- und *ngFor-Directive. *ngIf fügt ein DOM-Element hinzu oder entfernt eines. *ngSwitch verhält sich wie ein switch-Statement in JavaScript – abhängig von einer switch-Bedingung fügt es eines von mehreren möglichen Elementen in das DOM ein oder entfernt dieses. *ngFor stellt eine Schleife zur Verfügung, die für alle Elemente der Schleife einen HTML-Block definiert, den Angular rendert. Bei der *-Zeichenkette handelt es sich nicht um eine Template-Expression – vielmehr um eine eigene kleine Programmiersprache von Angular, die sogenannte Microsyntax.
Das Angular-CLI generierteine TypeScript-Klasse für die Directive mit dem Namen namedirective, erzeugt die zugehörige Unit-Testdatei und passt automatisch die App-Module-Datei an(Bild 7)
Simon
Angular unterstützt auch die Definition einer eigenen Attribut-Directive, sie besteht aus einer TypeScript-Klasse, die mit einem @Directive-Decorator versehen ist. Bei diesem Decorator definiert man über ein selector-Feld den Namen des zugehörigen Attributs; dieser sollte mit dem Präfix der Angular-App beginnen. Der Konstruktor der Klasse implementiert dann das gewünschte Verhalten der Directive. Am schnellsten erzeugt man die TypeScript-Klasse mit allen Abhängigkeiten der Directive über das Angluar-CLI: ng generate directive namedirective. Der Befehl erzeugt im app-Ordner des Projekts den Programmrahmen für die Klasse namedirective.directive.ts (Bild 7) sowie die zugehörige Unit-Test-Datei, zusätzlich trägt er die Deklaration der Directive-Klasse in das Root-Module (AppModule) ein.
Unterschiedliche Typen von Data-Bindung
Mit dem Grundkonzept Data-Binding (Datenbindung) kommunizieren Komponenten mit dem DOM der App. Sie bildet die Technik, um Daten mit der View einer Angular-App zu verbinden. Data-Bindung realisiert die Kommunikation zwischen dem TypeScript-Code einer Komponente und dem Template, das die App dem Benutzer anzeigt. Wegen dem mit Data-Binding verbundenen Mechanismus, muss man sich als Entwickler keine Gedanken machen, wie die Daten aus der View zu holen oder an diese weiterzureichen sind – beides erfolgt automatisch. Angular verbindet sich dazu mit den Eigenschaften und Events der nativen HTML-Elemente; aus diesem Grund kann man auch Elemente aus anderen Frameworks wie React oder Vue.js benutzen.Angular kennt zwei verschiedene Arten von Data-Binding: One-Way- und Two-Way-Data-Binding. Als einfache Art der Kommunikation findet One-Way-Data-Binding immer im HTML-Template statt, wenn sich der zugehörige TypeScript-Quellcode ändert – aktualisiert dies immer automatisch das DOM. Beim One-Way-Data-Binding benutzt man den zugehörigen Wert des Models in der View (der HTML-Seite), aber die View kann keine Aktualisierung des Models vornehmen. Typische Beispiele für One-Way-Data-Binding stellen String-Interpolation, Property- und Event-Binding dar.Unter String-Interpolation versteht man Platzhalter im HTML-Template (der View); sie zeigen Daten aus dem TypeScript-Code einer Komponente in der View über das HTML-Template an. Bei String-Interpolation kommt immer die toString-Funktion zum Einsatz – wenn es sich bei den Daten zum Zeichenketten handelt, dann sollte man String-Interpolation wählen, bei anderen Datentypen Property-Binding. Der Platzhalter befindet sich in doppelten geschweiften Klammern {{ daten }} direkt im HTML-Code – während die Komponente das Feld/Attribut daten definiert und über eine export-Anweisung zugänglich macht.Um Properties (Attribute) eines HTML-Elements zu verändern, setzt man Property-Binding ein. Innerhalb eines HTML-Template verwendet man das Property des HTML-Elements (zum Beispiel hidden) in eckigen Klammern [] und verknüpft dieses mit einem TypeScript-Attribut in doppelten Anführungszeichen: <p [hidden]=“verdeckt“>Dies ist ein neuer Absatz</p>.Die TypeScript-Klasse definiert als eigenes Property das Attribut “verdeckt“: Ändert sich das TypeScript-Attribut, so übernimmt das Property im HTML-Template dessen Wert.One-Way-Data-Binding
Die letzte Form des One-Way-Data-Bindings behandelt Events, die das DOM auslöst. Klickt beispielsweise der Endbenutzer eine Schaltfläche an, so löst das DOM einen Klick-Event aus. Ist dieser Event mit einer Methode verknüpft, so ruft das DOM die Methode auf und bringt diese zur Ausführung. Beim Event-Binding referenziert der Programmierer die vom Target-Event auszuführende Methode (das sogenannte Template-Statement); es steht in doppelten Anführungszeichen im HTML-Template beim zugehörigen HTML-Element: <button (click) = “loeschen()“ >Löschen> /button>. Die TypeScript-Klasse der Komponente definiert die vom Event auszuführende Methode, die eine export-Anweisung nach außen weiterreicht.Angular Update Guide erleichtert Migration
Für die Migration eines Projekts auf eine neue Angular greift man auf den CLI-Befehl ng update und die Website mit dem Angular Update Guide update.angular.io zurück. Das Angular-Team bei Google veröffentlicht in der Regel zwei neue Versionen pro Jahr. Als Long-Term-Support für eine konkrete Angular-Version gelten 18 Monate. Abhängig von der Weiterentwicklung und Wartung einer Angular-App beschäftigt sich deshalb ein Entwicklungsteam immer auch mit der Migration des Projekts auf eine neu Angular-Version.
Beim Two-Way-Data-Binding synchronisieren sich die Daten zwischen der TypeScript-Klasse und der Benutzeroberfläche (den HTML-Templates) automatisch: Änderungen spiegeln sich in den beiden Komponenten wider. Ändern sich die Daten in der TypeScript-Klasse, so übernimmt die Benutzeroberfläche diese Änderungen und umgekehrt. Dieses Verhalten findet sofort und automatisch statt, damit bleiben der TypeScript-Quellcode und das HTML-Template ständig aktuell. Two-Way-Data-Binding kombiniert das Property- mit dem Event-Binding. Für Two-Way-Data-Binding muss man die ngModel-Directive aktivieren. Two-Way-Data-Binding enthält auch das Formular-Package ein zusätzliches Modul von Angular – FormsModule genannt.Für die Verwendung von ngModel definiert man im TypeScript-Code der Modul-Datei einen import-Befehl für das FormsModule und übernimmt das FormsModule in das import:[]-Array:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule, FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Die Komponente in der TypeScript-Klasse gibt über die export class-Syntax den Namen der Variable, die über Two-Way-Binding synchronisiert werden soll, nach außen bekannt:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
variableName = 'Anfangswert';
loescheVariableName(): void {
this.variableName = '';
}
aendereVariableName(): void {
this.variableName = 'OTTO';
}
}
Anschließend hat der Programmierer vollständigen Zugriff auf diese Variable im zugehörigen HTML-Template der Komponente. Im HTML-Quellcode spricht man die Variable über das Property eines HTML-Elements wie folgt an: [(ngModel)]=“variableName“ – danach steht der Wert der Variable über den Ausdruck {{variableName}} im HTML-Quellcode zur Verfügung:
<h3>Beispiel für Two-Way-Data-Binding</h3>
<input type="text" name=“variableName“ [(ngModel)]="variableName">
<p> Ihre Eingabe: {{variableName}}</p>
<button (click)="loescheVariableName()">Löschen
</button>
<p>
<button (click)="aendereVariableName()"
>Ändere auf "OTTO"!</button>
</p>
Die Syntax zur Definition des Two-Way-Data-Binding beginnt mit [(ngModel)] – dies verdeutlicht die Kombination von Property-Binding [] mit dem Event-Binding – den runden Klammern (). Als visuelle Eselsbrücke spricht man von Banana in a Box: [( )]. Besitzt die Variable variableName einen Anfangswert, so erscheint dieser aufgrund des referenzierten Property des HTML-Elements sofort auf der HTML-Seite. Auch jeder seitens eines Endbenutzers geänderte Werts aktualisiert den Wert der Variable im Code der zugehörigen Komponente. Damit bekommt der Programmierer ein sehr mächtiges Konstrukt zur Synchronisierung der Programmdaten und der Benutzeroberfläche in die Hand.
Daten weiter- oder durchreichen
Um gemeinsame Daten zwischen verschiedenen Komponenten zu teilen, kann man immer auf einen Service zurückgreifen. Wie jeden Service deklariert man diesen über einen @Injectable-Decorator in der jeweiligen TypeScript-Klasse. Über die export-Anweisung macht man die Daten, deren Getter- und Setter-Methode sowie eventuell weitere Attribute oder Methoden für externe Klassen zugänglich. Alle Komponenten, die einen Zugriff auf diese gemeinsamen Daten erhalten sollen, gibt man den Service DatenService über ein import-Statement bekannt.Anschließend stehen die Daten den Komponenten über die exportierten Attribute oder Methoden des Services zur Verfügung. Zusätzlich registriert man den Service im Angular-Modulsystem über den @NgModule-Decorator im providers[]-Array: {provide: DatenService, useClass: DatenService}. Alternativ kann eine Registrierung über das Property providesIn: ‚root‘ beim @Injectable-Decorator erfolgen – diese Variante implementiert der Angular-Generator über den CLI-Befehl ng generate service Daten:
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DatenService {
constructor() { }
}
Für den Zugriff auf die Daten einer Komponente in deren HMTL-Template greift man auf die üblichen Angular-Konstrukte wie Template-Property, Data-Binding oder Microsyntax zurück. Besteht allerdings zwischen den Komponenten, die Daten austauschen, eine Eltern-Kind-Beziehung, so steht dem Programmierer ein spezielles Konstrukt zur Verfügung. Bei einer Eltern-Kind-Beziehung zwischen zwei Komponenten handelt es sich um eine Komponenten-Verschachtelung bei der sich auf der Benutzeroberfläche (View) außen die Eltern-Komponente und innen (innerhalb der HTML-Template der Eltern-Komponente) die Kind-Komponente befindet.Das Verfahren für den Datenaustausch nennt sich in Angular Komponenten-Interaktion und das damit verbundene Konstrukt: Input & Output. Sollen die Daten von der Eltern-Komponente an die Kind-Komponente also von außen nach innen durchgereicht werden, so greift man auf den @Input-Decorator und Property-Binding zurück. In der Kind-Komponente deklariert man den @Input()-Decorator mit einer Variable für den Platzhalter der Daten im HTML-Template:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-zaehler',
templateUrl: './zaehler.component.html',
styleUrls: ['./zaehler.component.css']
})
export class ZaehlerComponent implements OnInit {
@Input() zaehler: number;
@Output() zaehlerChange: EventEmitter<number> = new EventEmitter<number>();
erhoehe(): void {
this.zaehler++;
this.zaehlerChange.emit(this.zaehler);
}
constructor() { }
ngOnInit(): void { }
}
Für die Eltern-Komponente führt man beim zugehörigen HTML-Template das Property-Binding durch:
<h2>Beispiel Komponenten-Interaktion</h2>
<app-zaehler [(zaehler)]="zaehler"></app-zaehler>
<p> Aktueller Wert: {{zaehler}}</p>
<button (click)="loescheZaehler()">Lösche</button>
Achtung: Beim Einsatz der Microsyntax mit [(ngModel)] muss man zusätzlich noch einen Import des Angular-FormsModule in der app.module.ts-Datei durchführen.Um umgekehrt, Daten vom Kind an die Eltern-Komponente durchzureichen, benutzt man einen @Output()-Decorator mit Event-Bindung und EventEmitter in der Kind-Komponente. In diesem Fall wartet die Eltern-Komponente auf das Kind-Event. In der Kind-Komponente deklariert man den @Output-Decorator, legt einen EventEmitter an und definiert die beim Eintreten des Events auszuführende Methode. In der Eltern-Komponente definiert man den Event-Handler für den Target-Event bei der TypeScript-Klasse auf den man im HTML-Template verweist.Dabei nimmt man in beiden HTML-Templates der Eltern- und der Kind-Komponente ein Event-Bindung vor mit dem Target-Event (click) und dem Template-Statement loescheZaehler() beziehungsweise erhoehe():
<div>
Kind-Komponente
<p>
Count: {{ zaehler }}
<button (click)="erhoehe()">Erhöhe den Wert</button>
</p>
</div>
Für jede Website stellt der Webbrowser als direkt verfügbares Feature eine Navigation bereit: Gibt der Endbenutzer eine URL in dessen Adresszeile ein, so navigiert der Webbrowser auf die zugehörige Webseite. Klickt man Links (Verknüpfungen) innerhalb einer Webseite an, so navigiert der Webbrowser auf die damit verbundene Seite. Über die Schaltflächen Rückwärts (Eine Seite zurück) und Vorwärts (Eine Seite vor) navigiert der Webbrowser rückwärts oder vorwärts durch die Chronik (auch History oder Verlauf genannt) der bisher angezeigten Webseiten. Zwei in Angular implementierte Module RouterModule und Routes greifen auf dieses Verhalten des Webbrowsers über eine Angular-App zu.
Angular kennt zwei Modulsysteme: ES6 & NgModule
Angular führt verschiedene Aufgaben über zwei getrennte Modulsysteme durch: Das ES6-Modulsystem sorgt für die Einhaltung der Struktur im Dateisystem, steuert das Laden von Code und verhindert globale Variable. Die NgModule (Angular-Module) führen die Registrierung der Komponenten durch und binden diese in die Funktionalität der Angular-App ein. Ausgangspunkt einer Angular-App stellt das Root-Modul dar – weitere Moduln werden direkt oder gegebenenfalls über Lazy-Loading geladen. Achtung: Das Laden sämtlicher Angular-Module erfolgt immer über das ES6-Modulsystem.
Das Routing (Navigation) als Bestandteil einer Angular-App verantwortet die Verknüpfung zwischen den URLs und den HTML-Templates. Abhängig von der Auswahl eines Benutzers führt das Routing die Navigation zwischen verschiedenen HTML-Seiten der Web-App durch. Allerdings gehört das Routing nicht zu den Bestandteilen der Angular-Core-Packages; vielmehr befindet es sich in einem eigenständigen Module @angular/router, das man über die Anweisung import { RouterModule, Routes } from ‚@angular/router‘ bereitstellt.Der Generator des Angular-CLI erzeugt diese import-Anweisung für das Routing-Module: ng generate module nameRouting --routing; trägt allerdings keinerlei Abhängigkeiten automatisch in der Root-Komponente/Module der App ein. Dies erfolgt nur bei der Neuanlage einer App zusammen mit dem Routing-Module. Standardmäßig verwendet Angular die mit HTML5 gängigen relativen Links über eine Basis-URL; der dazu notwendigen <base href=“/“>-Tag befindet sich in der index.html-Datei der App.Als Programmierer implementiert man das Routing einer Web-App in speziellen Router-Moduln; über diese stellt man der App auch alle Routes zur Verfügung. Eine Route beschreibt die Beziehung zwischen einer URL und einer Komponente der App. Die Definition einer Route erfolgt in einer Route-Definition-Tabelle; ihre einfachste Form enthält den relativen Pfad und die Referenz auf eine Komponente. Die Definition der Route-Tabelle erfolgt am besten in einer eigenständigen TypeScript-Klasse – dabei beeinflusst die Reihenfolge der Einträge in der Array-Datenstruktur, welchen Router Angular anzieht:
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent} from './home/home.component';
import { KontaktComponent} from
'./kontakt/kontakt.component';
import { ProductComponent} from
'./product/product.component';
import { FehlerComponent} from
'./fehler/fehler.component';
export const appRoutes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'kontakt', component: KontaktComponent },
{ path: 'produkt', component: ProductComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: '**', component: FehlerComponent }
];
Es kommt immer der Router zur Ausführung, der als erstes die im path-Key angegebene Bedingung erfüllt.
Typen von Routes und ihre Bereitstellung
Die Array-Datenstruktur heißt in der Angular-Terminologie Route-Configuration-Object oder kurz: Route-Object. Jede Route besitzt verschiedene Eigenschaften, über die man eine Konfiguration vornimmt und damit das Verhalten der Route definiert. Erster Parameter stellt der relative Pfad dar, der das URL-Pfadsegment repräsentiert. Daran schließt sich als zweiter Parameter der Name der durch die Route anzuzeigende Komponente an. Als wichtigen Typ von Routes unterscheidet man die Default-Route (manchmal auch Home-Route genannt); diese spezifiziert eine im Pfad angegebene leere Zeichenkette. Die Router-Klasse implementiert eine navigate()-Methode, um über ein Router-Objekt dynamisch zu einer URL zu navigieren.Um die Default-Route auf einen anderen Pfad umzuleiten, definiert man bei ihr als zweiten Parameter redirectTo: ‚home‘ an, so dass die path: ‚home‘-Eintrag greift und die App die mit ihm verbundene Komponente anzeigt. Da jede Route mit einer leeren Zeichenkette endet, muss man als dritten Parameter bei der Default-Route pathMatch: ‚full‘ setzen. Ein anderer wichtige Route-Typ definiert die Wildcard-Route (‚**‘), diese wird von jeder URL immer unabhängig von ihrem Inhalt erfüllt. Insofern muss man die Wildcard-Route im Array des Route-Objects immer an letzter Stellte positionieren. Dieser Eintrag im Route-Objekt fängt fehlerhafte URLs, die eigentlich zu einem Fehler in der App führen würden, durch Ausgabe einer Error-Komponente ab.Damit die Angular-App alle vorhandenen Routes für die Navigation nutzen kann, macht man diese über ein speziell für die App definiertes Routing-Module (AppRoutingModule) zugänglich:
@NgModule({
imports: [
RouterModule,
RouterModule.forRoot(appRoutes)
],
providers: […],
exports: [RouterModule],
})
export class AppRoutingModule { }
Dieses Modul dient quasi als Angular-Service und registriert die eingerichteten Route-Objects als Router-Service für die App. Dazu greift man innerhalb des AppRoutingModule beim import-Statement auf die forRoot()-Methode zurück. Diese ruft man beim Import für das Angular-RouterModule auf und übergibt der Methode forRoot() das Array mit den Routes (appRoutes). Damit die App auf die eingerichteten Routes im angepassten RouterModule von Angular zugreifen kann, muss man es zum Schluss exportieren.
Links für Click-Event einrichten
Die Benutzeroberfläche der App verknüpft ein geeignetes HTML-Objekt zum Beispiel eine Schaltfläche, ein Bild oder ähnliches mit dem URL-Link. Empfehlenswert ist der Einsatz der CSS-basierten Gestaltungsvorlagen von Bootstrap, die man am schnellsten über ein CDN (Content Delivery Network) in der index-HTML einbindet: <link href=“https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css“ rel=“stylesheet“>.Die Verknüpfung der URL mit der Route realisiert man in der Angular-App über ein Array mit Link-Parametern, das man an die routerLink-Directive übergibt, beispielsweise: <li><a [routerLink]=“[‘product‘]“>Product</a></li>:
<!-- Die CSS-Gestaltungsvorlagen von Bootstrap einbinden -->
<div class="container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" [routerLink]="['/']"><strong> {{title}} </strong></a>
</div>
<ul class="nav navbar-nav">
<li><a [routerLink]="['home']">Home</a></li>
<li><a [routerLink]="['produkt']">Produkt</a></li>
<li><a [routerLink]="['kontakt']">Kontakt us</a></li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
</div>
Um der App beim Navigieren über die Route einen Parameter in der URL mitzugeben, reicht man diesen in der Route-Definition über eine String-Variable weiter, zum Beispiel: [routerLink]=“[‘product‘, productnumber]“.Sobald der Endbenutzer in der App eine Navigation über die Route (hier: product) anfordert, durchsucht der Router das Routes-Array und zeigt die mit der Route (hier: product) verbundene Komponente in der App an. Gleichzeitig aktualisiert die App die URL in der Adressleiste des Webbrowsers und dessen Chronik (History). Der Programmierer muss weder die RouterLink- noch die RouterOutlet-Directive in die Web-App importieren, da dieser Import automatisch über das RouterModule erfolgt. Die eigentliche Anzeige der über das Routing verbundenen Komponente erfolgt über die RouterOutlet-Directive im HTML-Template der App: <router-outlet></router-outlet>.Die RouterOutlet-Directive beschreibt Angular, an welcher Stelle auf einer Webseite die Komponente angezeigt werden soll. Gibt man bei der Definition einer Route in der Route-Definitions-Tabelle ein outlet-Attribut an: { path: ‘product‘, component: ProductComponent, outlet: “admin“ }, ermöglicht dies die gleichzeitige Ausführung mehrerer Routes. Die Route mit dem outlet-Attribut bezeichnet man in der Angular-Terminologie als Auxiliary-Route, sie unterteilt eine Webseite in verschiedene Bereiche.Zusätzlich kommt dem ActivatedRoute-Objekt und der RouterLinkActive-Directive eine besondere Rolle zu. Das ActivatedRoute-Objekt repräsentiert die aktuell aktivierte Route, die mit der geladenen Komponente verknüpft ist. Mit der RouterLinkActive-Directive fügt man Klassen einem HTML-Element, das mit einem RouterLink verbunden ist, hinzu oder entfernt diese aus dem DOM. Mit RouterLinkActive hebt man die aktive Route anhand des zugehörigen Elements auf der Benutzeroberfläche hervor, beispielsweise durch eine spezielle Kennzeichnung mit einer grauen Hintergrundfarbe.
Lebenszyklus einer Angular-Komponente
Jedes Framework/Plattform implementiert seine Ausführung über Objekte, deren Management durch die Laufzeitumgebung erfolgt. Entsprechend erzeugt und verwaltet Angular eine Menge von Objekten während der Laufzeit einer App. Um das Management und die damit verbundenen Effekte und Abhängigkeiten aller Objekte (Komponenten, Directiven) zu vereinfachen, kommt das Konzept des Lebenszyklus einer Komponente zum Tragen. Innerhalb eines Lebenszyklus einer Komponente müssen bestimmte Aufgaben von Angular und der App ausgeführt werden.Die Einteilung des Lebenszyklus einer Komponente orientiert sich an verschiedenen Status (Stati). Sobald eine Änderung im Status einer Komponente erforderlich ist oder festgestellt wird, nimmt Angular diese Änderung vor und teilt diese allen anderen Komponenten (die davon Kenntnis erlangen müssen) mit. Die Einteilung der Phasen oder Zustände des Lebenszyklus einer Komponente erfolgt in der Reihenfolge ihrer Ausführung (Aktivierung). Als erstes erzeugt Angular eine Komponente über deren Konstruktor, anschließend startet der ständig stattfindende Prozess zur fortlaufenden Beobachtung (Monitoring) von Änderungen an der Komponente.Ein weiterer wichtiger Zustand ist die Fertigstellung einer Komponente, so dass diese von anderen innerhalb der App verwendet werden kann. Erkennt Angular, dass keine andere Komponente mehr die gerade betrachtete Komponente benötigt, so entfernt Angular diese aus dem Komponenten-Pool. Der Lebenszyklus einer Komponente bildet den kompletten Lebenszeitraum einer Komponente ab. Als Programmierer erhält man Zugriff auf die wichtigsten Zustände. Der Wechsel einer Komponente von einem aktuellen Zustand in einen anderen löst ein Ereignis aus.
Die Phasen im Lebenszykluseines Objekts der Angular-Laufzeitumgebung kann man in drei Gruppen einteilen: Creates, Changes und Destroys(Bild 8)
Simon
Um auf diese Ereignisse reagieren zu können, stellt Angular verschiedene Interfaces zur Verfügung, die man als Programmierer implementieren kann. Die zugehörigen Interfaces (häufig Hooks genannt) lauten (in der Reihenfolge ihres Auftretens) (Bild 8): OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked und OnDestroy. Die Implementierung eines derartigen Interface nimmt man über eine gleichnamige Funktion vor, die als Präfix die beiden Buchstaben ng erhält. Beispielsweise heißt für das Interface OnInit die zugehörige Funktion ngOnInit. Lifecycle-Hooks optimieren das Verhalten der Komponenten, während ihrer Erstellung, Aktualisierung und Zerstörung.
Einsatzszenarien von Hooks
Im Unterschied zu den Komponenten über die man Zugriff auf alle Lifecycle-Hooks hat, können in Directiven nur ngOnChanges, ngOnInit, ngDoCheck und ngOnDestroy verwendet werden. Die anderen vier Hooks (AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked) beziehen sich auf die View und stehen deshalb nur in Komponenten zur Verfügung. Während der Ausführung eines Hooks lässt sich die Anwendungslogik steuern. Beispielsweise die Initialisierung der Logik während der Instanziierung einer Komponente oder die Beendigung der Logik während ihrer Beseitigung.Der erste zur Ausführung kommende Hook stellt ngOnChanges dar, er wird aufgerufen, wenn der Wert einer gebundenen Property sich ändert. Ist keine Änderung festzustellen, dann wird dieser Hook nicht aufgerufen. Während ngOnChanges bei jeder Änderung ausgeführt wird, ruft Angular ngOnInit nur einmalig nach der ersten Änderung auf. ngOnInit bietet sich zur Bereitstellung der benötigten Daten für eine Komponente oder Directive an. Der Hook ngOnDestroy wird aufgerufen, wenn eine Komponente aus der Laufzeitumgebung zu entfernen ist – damit eignet sich dieser Hook, um Aufräumarbeiten innerhalb der App durchzuführen. Am häufigsten kommen die Lifecycle-Hooks OnInit, OnChanges und OnDestroy zum Einsatz.Um die Integrität der Logik einer Anwendung zu gewährleisten, kann es während dem Überprüfen der Komponente oder Directive notwendig sein, Logikprüfungen in den Hooks DoCheck, AfterContentChecked und AfterViewChecked auszuführen. Die drei Hooks führen Prüfungen auch ihrer Kind-Komponenten durch, selbst wenn es bei der eigentlichen Komponente zu keiner Änderung kam. ngDoCheck kann im Bedarfsfall bei aufgetretenen Änderungen angestoßen werden, die der automatischen Change-Detection von Angular nicht erkannt hat. Die anderen Hooks OnInit, AfterContentInit und AfterViewInit ermöglichen es, beim ersten Rendern der Komponente die mit ihr verbundene Programmlogik auszuführen; dazu gehört auch das Einrichten der zugehörigen Datenbestandteile.Formularverarbeitung mit Angular
Formulare bilden einen wichtigen Teil jeder Anwendung, deren Benutzeroberfläche mit Daten arbeitet. Die Angular-Plattform verfügt über zwei verschiedene Ansätze zur Realisierung von Formularen: Template-Driven- und Reactive-Form. Die einfachste Art, um ein Formular für eine Angular-App zu realisieren, ist der Template-Driven-Ansatz. Er stammt noch aus der Zeit von AngularJS (dem Vorgängersystem der heutigen Angular-Plattform). Beim Template-Driven-Ansatz implementiert der Programmierer die komplette Logik des Formulars über Directiven im HTML-Template.Im Unterschied zur Reactive-Form (diesen bezeichnet man auch als Model-Driven-Ansatz) bei der ein Entwickler die Logik eines Formulars in der zugehörigen Komponente als ein Objekt definiert. Im Model-Driven-Ansatz legt man die Repräsentation des Formulars (das Form-Model) in der Komponenten-Klasse an: Man definiert Formularfelder als Properties/Attribute der Komponenten-Klasse. Angular bindet die Daten dieses Modells im Template über Directiven an HTML-Elemente. So erzeugt man in beiden Fällen angelehnt an HTML-Formulare sehr leicht eigene Formulare, ohne eine Zeile JavaScript-Code zu schreiben. Diese Vorgehensweise erleichtert den Test von Reactive-Forms gegenüber den Template-Driven-Formularen, erschwert allerdings die Verwaltung deren Status.Beide Formulartechniken Template-Driven- als auch Reactive-Forms basieren auf den Grundbausteinen: FormControl, FormGroup und FormArray. Lediglich die Programmierung im Quellcode vor allem die Anlage der Bestandteile des Formulars und deren Einbettung in das HTML-Template unterscheidet sich. Das FormControl repräsentiert ein einzelnes Eingabefeld eines Formulars. Eine Sammlung von FormControls bildet eine FormGroup. Ein FormArray stellt ein Array/Feld von FormControls dar. Anstatt über einen Namen wie bei einer FormGroup greift man auf ein FormArray einer FormControl über einen Index zu.Um ein Formular in Angular zu programmieren, stellt man das FormsModule für Template-Driven und das ReactiveFormsModule für Reative-Driven-Forms über einen Import bereit. Als Entwickler kann man auf die Eingabewerte eines Formulars zugreifen und deren Konsistenz überprüfen. Im Template-Driven-Forms findet das mittels HTML5-Validation innerhalb des HTML-Template über Validatoren statt. Angluar kennt diese als HTML-Attribute und fügt die Prüffunktionen der FormControl-Instanz hinzu. Bei Reactive-Driven-Forms greift man über das value-Feld eines Eingabefeld beziehungsweise bei einem FormArray über die get()-Funktion auf dessen Wert zu.Zusätzlich kann man in beiden Formulartechniken den Status eines Eingabefelds über Check-Validation prüfen; dieser befindet sich in der Liste errors oder den drei booleschen Feldern dirty, touched und valid. Während errors eine Fehlerliste zurückliefert, erhält man bei dirty, touched und valid den Wert true, wenn der Eingabewert sich geändert (dirty), das Eingabefeld unverändert den Input-Fokus hatte (touched) und alle Prüfungen für den Eingabewert erfolgreich waren (valid). Diese booleschen Werte befinden sich als Attribute bei den Eingabefeldern; heißt zum Beispiel das Eingabefeld vorname so erreicht man die Fehlerliste über vorname.errors oder den dirty-Status über vorname.dirty.In der Regel führt man Konsistenzprüfungen der Eingabefelder in der Angular-App und nicht über die Browser-Prüfungen durch. Insofern schaltet man die eingebauten HTML5-Prüfungen über das novalidate-Attribut beim form-Element aus. Für die Prüfung der Eingabefelder auf Konsistenz besitzt Angular fertige Built-In-Validatoren: required, minlength, maxlength, pattern und weitere. Bei der Anlage eines Eingabefelds verknüpft man dieses mit allen gewünschten Angular-Validatoren. Eine Prüfung liefert eine Fehlerliste oder den Null-Wert bei deren erfolgreichem Bestehen zurück. Sollten die vorhandenen Validatoren nicht alle Anforderungen erfüllen, kann man auch eigene, sogenannte Custom-Validatoren programmieren.Entwicklung eigener Custom-Validatoren
In einem Angular-Projekt sollte man für eigene Prüfungen der Konsistenz von Formulardaten einen eigenen Ordner zum Beispiel validators im Verzeichnis des zugehörigen Formulars anlegen. Den selbst programmierten Validator realisiert eine TypeScript-Klasse, die man in den Ordner mit den Konsistenzprüfungen anlegt. Grundsätzlich benötigt man zur Realisierung einer Konsistenzprüfung die Klassen AbstractControl, Validator und ValidationErrors aus dem Angular-FormsModule. Die Prüfroutine implementieren das ValidatorFn-Interface und erhält dazu ein Formular-Control (AbstractControl) als Eingabeparameter. AbstractControl stellt die Oberklasse eines jeden Formular-Controls dar, sie reicht das zu prüfende Eingabefeld des Formulars an die Prüfroutine weiter. ValidationErrors entspricht einer Fehlermeldung, welche man nach fehlerhafter Prüfung als Rückgabe-Objekt erhält. ValidationErrors enthält Schlüssel-Werte-Paare, die der Programmierer selbst vorgibt. Dabei kann an erster Position der Name der Prüfroutine (mit einem Fehlertext als Zusatzinfo) und an zweiter Position der erwartete Wert stehen. Besteht das AbstractControl alle Prüfungen eines Validators, so liefert die Prüfroutine den Null-Wert zurück:
static gueltig(control: AbstractControl):
ValidationErrors | null {
const wert = +control.value;
if (isNaN(wert)) {
// Fehlerfall: Keine Zahl eingegeben
return { appGueltig: 'Keine Zahl eingegeben',
requiredValue: 10 }; }
if (wert <= 10) {
// Fehlerfall: Zahl kleiner oder gleich 10
return { appGueltig: 'Zahl kleiner oder gleich 10
eingegeben', requiredValue: 10 }; }
// Kein Fehler aufgetreten
return (null);
}
Alternativ kann man einen Custom-Validator als Directive programmieren, so dass die Prüfroutine als Attribut bei einem input-DOM-Element zum Einsatz kommt. In diesem Fall legt man im Ablageordner mit den Prüfungen keine TypeScript-Klassen, sondern eine Angular-Directive ab. Die Prüfroutine befindet sich jetzt in einer TypeScript-Klasse bei der zugehörigen Directive. Die Directive nimmt man als zusätzliches Modul in die Deklarationen des AppModule auf.Damit Angular die Directive als Validator erkennt, greift man auf den Provider NG_VALIDATORS zurück. Importiert man diesen bei der Directive (import { NG_VALIDATORS } from ‚@angular/forms‘;) und trägt ihn in das providers-Array ein, so injiziert Angular den selbst-programmierten Validator in diesen Provider und führt für die referenzierenden Formularfelder die Prüfroutinen aus. Beim Import der Klasse Directive aus @angular/core muss zusätzlich forwardRef angegeben werden, da die providers-Klausel auf eine noch nicht definierte TypeScript-Klasse über useExisting verweist:
import { Directive, forwardRef } from '@angular/core';
import { NG_VALIDATORS, AbstractControl,
ValidationErrors, Validator, FormControl } from
'@angular/forms';
@Directive({
selector: ‚[appPruefeValidator]‘,
providers: [
{ provide: NG_VALIDATORS, useExisting:
PruefeValidatorDirective, multi: true }
]
})
export class PruefeValidatorDirective implements
Validator {
validate(control: AbstractControl): ValidationErrors |
null {
return PruefeValidatorDirective.gueltig(control);
}
static gueltig(control: AbstractControl): ValidationErrors | null {
…
}
Die eigentliche Ausgabe des Formulars übernimmt das HTML-Template; es stößt auch die Prüfroutine an und führt zusätzlich gängige eigene Prüfungen durch.Beispielsweise prüft sie, ob der Benutzer im Formularfeld den Eingabewert geändert hat (dirty). Im Template stehen neben den Angular-Directiven auch alle Tags für HTML-Formulare zur Verfügung.
Links zum Thema
<b>◼ <a href="https://angular.io/" rel="noopener" target="_blank">Homepage von Angular</a> <br/></b>