In diesem Artikel wollen wir eine bestehende kleine Cross-Plattform Angular-Anwendung verwenden und um die Vorteile von Progressive Web Apps anreichern. Die Beispielanwendung nutzt die Star-Wars- und die Poké-APIs, um beispielhaft Master-Detail-Daten anzuzeigen (Abb. 1). Als Buildsystem wird die aktuelle Release Candidate Version 1.6.0-RC.1 von Angular CLI eingesetzt, um Vorteile wie die Ahead-of-Time Compilation oder Tree Shaking zu nutzen. Beides hilft uns, eine in Bezug auf die Dateigröße möglichst kleine und performante Anwendung zu entwickeln. Die bestehende Anwendung inkludiert sowohl das Tool Cordova, sodass sie auch als native Anwendung verpackt und auf mobilen Geräten mit Android oder iOS als echte Smartphone-App deployt werden kann. Zusätzlich hilft Cordova beim Benutzen echter Plattform-APIs, wie z.B. dem Teilen-API, um Inhalte mit unseren Freunden via Facebook, Twitter, WhatsApp und allen anderen Apps, die diese Funktion unterstützen, teilen zu können. Auch inkludiert die bestehende Anwendung das Tool Electron, um eine echte native Desktopanwendung, sprich .exe, .App und Co., zu generieren und auch hier native Funktionen zum Teilen des Inhalts anzusprechen.
Doch wie können wir zukünftig Apps entwickeln, die ohne weiteres Tooling, wie Cordova oder Electron, auskommen und dennoch auf native Funktionen zugreifen können? Hier kommen die Progressive Web Apps (PWA) ins Spiel. Die vollständige Beispielanwendung für diesen Artikel liegt auf GitHub. Es empfiehlt sich, den Code zum Artikel zu öffnen, da im Artikel nur Ausschnitte gezeigt werden können. Progressive Web App ist ein Begriff, den man in der Webwelt aktuell oft hört und auf den bereits große Firmen wie beispielsweise Twitter, Telegram oder die NASA aufgesprungen sind. Doch um was genau handelt es sich überhaupt?
Der Blick in die Zukunft
Progressive Web Apps beschreibt eine Sammlung von Technologien, Möglichkeiten und Eigenschaften zukünftiger Webanwendungen. PWAs selbst sind keine eigene Technologie, sondern eine seit 2015 von Google getriebene Initiative, die das Web nativer machen soll und dabei natürlich auch Zugriff auf native Gerätefunktionen wie die Kamera gewährt. Im Kern sind PWAs ganz normale Webanwendungen, wie auch unsere Beispielanwendung eine ist – aber auf Steroiden. Die bestehende Webanwendung wird mit weiteren nativen Funktionen angereichert. Um das zu erreichen, legt Google einige Charakteristika fest, die eine Anwendung haben sollte, um als PWA zu gelten. Schauen wir uns im Folgenden die einzelnen Charakteristika genauer an.
- Progressive bedeutet, dass eine bestehende Webanwendung um weitere Funktionen angereichert, also progressiv verbessert wird. Das bedeutet, dass Benutzer mit modernen Browsern die Webanwendung mit den verbesserten Funktionen nutzen können. Benutzer mit älteren Browsern oder Browsern, die gewisse Standards noch nicht implementiert haben, können die Webanwendung ohne Zusatzfunktionen weiterhin benutzen.
- Responsive bedeutet, dass die Anwendung sich an jeden Formfaktor, also vom Smartphone über Tablets bis hin zum Desktop, anpasst und dem Benutzer eine für den Formfaktor angepasste User Experience und ein entsprechend angepasstes User Interface bieten kann. Unsere Cross-Plattform-Beispielanwendung ist dank Flexbox bereits vollständig Responsive; daher haben wir diese Charakteristik bereits erfüllt.
- Connectivity Indepenent bedeutet, dass die Webanwendung auch ohne oder mit schlechter Internetverbindung genutzt werden kann, also offlinefähig ist. Um das zu erreichen, werden wir im Artikel den Service Worker kennenlernen – einen zweiten JavaScript-Thread.
- Die Charakteristik App-like beschreibt, dass eine Anwendung sich wie eine native Anwendung anfühlen und verhalten soll. Single-Page Applications (SPA) unterstützen diese Charakteristik sehr gut, da eine SPA auf dem Endgerät ausgeführt wird. Das hilft bei der Navigation, da nur bestimmte Seiteninhalte ausgetauscht werden und dies animiert werden kann – wie eben in der nativen Anwendung. Da unsere Beispielanwendung eine SPA ist und ein clientseitiges Routing durchführt, erfüllen wir diese Charakteristik ebenfalls.
- Fresh beschreibt, dass eine Anwendung immer aktuell ist. Das spiegelt sich zum einen in den Daten der Anwendung wider. Zum anderen ist auch die Anwendung selbst gemeint; sie wird beispielsweise durch ein Hintergrundanwendungsupdate aktualisiert, das über den bereits angesprochenen Service Worker erledigt werden kann.
- Safe beschreibt, dass eine Anwendung nur über HTTPS – mit Ausnahme von localhost zur Entwicklung – zugänglich ist. Das soll gewährleisten, dass niemand die Daten auf Transportebene abgreifen oder gar verändern kann.
- Unter der Charakteristik Discoverable versteht man das Auffinden von PWAs via Suchmaschinen, beispielsweise Google oder Bing. Das wiederum bedeutet, dass PWAs nicht extra in einen App Store veröffentlicht werden müssen, sondern eine Standardsuchmaschine die Anwendungen finden und indizieren kann.
- Die Charakteristik Re-engagable beschreibt das aktive Zurückholen des Benutzers zur Anwendung. Typisches Beispiel ist hier die Nutzung von Push-Notifikationen, wie man sie von Facebook, WhatsApp oder Twitter kennt, sodass der Benutzer sich erneut mit der Anwendung beschäftigt.
- Installable beschreibt, dass Benutzer für sie interessante Anwendungen ohne einen App Store installieren können. Das bedeutet für den Benutzer, dass er die Webanwendung, die er gerade benutzt, direkt aus dem Web heraus installieren kann, ohne dass sie erneut im App Store gesucht werden muss.
- PWAs sollen Linkable sein, also einfach via URL geteilt werden können, ohne dass eine Installation der Anwendung erforderlich ist. Da unsere Beispielanwendung vollständig verlinkbar ist, haben wir diese Charakteristik erfüllt.
Wie man sieht, beschreiben die Charakteristika hauptsächlich nichtfunktionale Anforderungen, die durch technische Hilfsmittel umgesetzt werden können. Da wir unsere Beispielanwendung bereits als moderne Webapplikation erstellt haben, haben wir einige Charakteristika ohne weiteres Zutun bereits erfüllt. Prinzipiell benötigt man kein Framework wie Angular oder React, um eine PWA zu erstellen. Allerdings unterstützen SPA-Frameworks generell die Entwicklung von großen Webanwendungen, die um die Charakteristika von PWAs ergänzt werden können. Bevor wir uns im Folgenden mit den weiteren Charakteristika speziell in Bezug auf unsere Angular-Anwendung beschäftigen, werfen wir einen Blick auf den zuvor angesprochenen Service Worker.
Der Rechenknecht im Hintergrund
Der Service Worker erweitert unsere Anwendung um einen zweiten, im Hintergrund laufenden JavaScript-Thread, mit dem eine bidirektionale Kommunikation möglich ist. Im Hintergrund laufend bedeutet, dass der Service Worker auch aktiv ist, wenn unsere Anwendung nicht läuft. Facebook ist hier ein gutes Beispiel: Auch wenn in keinem Tab Facebook geöffnet ist, werden uns Notifikationen angezeigt. Hier werkelt ein Service Worker im Hintergrund. Es nutzen bereits erstaunlich viele Websites einen Service Worker, um dem Benutzer einen gewissen Komfort zu ermöglichen.
Der JavaScript & HTML5 Track auf der BASTA!
Generell sind PWAs und die dahinterstehenden Technologien sehr modern, sodass viele Browser noch in der Konzeptions- bzw. Implementierungsphase der Features sind. Allen voran Google Chrome und damit auch Android. Sie haben bisher die weitreichendste Unterstützung der Technologien für PWAs. Ganz hinten steht der neue Internet Explorer: Safari. Safari bietet leider kaum die für PWAs nötigen Technologien an. Einige Features sind im Fünfjahresplan angedacht, aber es existieren noch keine konkreten Implementierungspläne.
Doch welche Aufgaben kann der Service Worker für uns erledigen? Der Service Worker ist speziell darauf ausgelegt, asynchrone Arbeiten durchzuführen. Daher hat er auf synchrone (DOM-)APIs wie z. B. der localStorage keinen Zugriff. Jedoch eignet er sich besonders für Arbeiten wie das Synchronisieren von Daten in einer IndexedDB, das Cachen von Dateien und Daten zur Offlineverwendung, als Request-Proxy zwischen der Anwendung und dem Internet oder – für das Re-engagement – zum Anzeigen von Desktopnachrichten nach Erhalt von Push-Nachrichten. Schauen wir uns einmal an, was das fleißige Kerlchen für unsere Anwendung tun kann.
Let’s go: Offline
Wir werden im Folgenden unsere Beispielanwendung um zwei Dinge erweitern: Offlinefähigkeit und Push-Benachrichtigungen. Zum Testen der Implementierung empfiehlt sich die Installation von Google Chrome Canary, da dieser, wie eingangs beschrieben, die beste Unterstützung der nötigen Technologien für PWAs bietet. Zudem schauen wir uns die PWAs mit einem starken Fokus auf Angular an. Doch aufgepasst! Wie anfangs erwähnt müssen wir auf aktuelle Betas und Release Candidates von Angular zurückgreifen, um Unterstützung für den Service Worker zu erhalten. Die Funktionen sind daher noch sehr neu und die Unterstützung für uns Entwickler teilweise noch sehr rudimentär. Diese Dinge werden sich in der Zukunft verändern, verbessern und vereinfachen. Cutting Edge eben.
Durch existierendes Tooling werden wir uns eher an der Nutzung der PWA-Technologien orientieren und weniger auf Low-Level-Konzepte eingehen. Hier lohnt es sich, einen Blick in das Mozilla Developer Network zu werfen. Die Links hierfür sind am Ende des Artikels zu finden.
Da das Thema Offline selbst einen weiteren Artikel füllen würde, wollen wir es auf recht einfachem Wege implementieren: Wir werden die gesamte Anwendung sowie die Antworten von API-Anfragen im Cache ablegen. Damit erreichen wir, dass unsere Anwendung im Browser auch ohne Internetverbindung funktioniert und ein kleiner Teil an Daten zur Verfügung steht. Den Service Worker werden wir hierbei als Proxy benutzen, der alle HTTPS-Anfragen der Anwendung entgegennimmt und aus dem Browser Cache beantwortet, sofern die Daten dort bereits vorhanden sind. Falls nicht, wird er die Daten aus dem Internet laden und deren Antwort im Cache ablegen. Dann mal los! Zu Beginn müssen wir ein neues Paket installieren, um Angular mit dem Service Worker bekanntzumachen: npm i @angular/service-worker@next.
Zusätzlich müssen wir die Datei .angular-cli.json editieren und dem Abschnitt apps folgende Eigenschaft hinzufügen:
"apps": { // ... "serviceWorker": true }
Die Eigenschaft serviceWorker schaltet die Service-Worker-Unterstützung des Angular-CLIs an, das uns eine Menge Arbeit abnehmen wird. Welche genau, werden wir gleich im Anschluss sehen. Im Ordner src legen wir eine Datei mit dem Namen manifest.json an. Der Inhalt ist Listing 1 zu entnehmen.
Listing 1: Inhalt der Datei src/manifest.json
{ "short_name": "WinDev App", "name": "Windows Developer PWA Sample", "icons": [ { "src": "assets/launcher-icon-3x.png", "sizes": "144x144", "type": "image/png" }, { "src": "assets/icon-1024px-square.png", "sizes": "1024x1024", "type": "image/png" } ], "theme_color": "#ff584f", "background_color": "#ffffff", "start_url": "/index.html", "display": "standalone" }
Generell benötigt jede PWA eine manifest.json-Datei. In ihr sind Metadaten über die Anwendung enthalten. Schauen wir uns einmal genauer an, welche Inhalte in dieser Datei zu finden sind:
- short_name: Kurzname der Anwendung; sollte nicht länger als zwölf Zeichen sein, um ein Abschneiden des Anwendungsnamens auf dem Homescreen des Smartphones zu vermeiden.
- name: Langname der Anwendung.
- icons: Array von beliebig vielen Icons, bestehend aus Quelldatei, Größe und Typ.
- theme_color: Hauptfarbe der Anwendung. Bestimmt bei manchen Betriebssystemen, wie die Anwendung dargestellt wird. Bspw. wird hierdurch die Farbe im Android Task Switcher geändert.
- background_color: Anweisung für den Browser, mit welcher Hintergrundfarbe die App gezeichnet werden soll, bevor etwaige CSS-Dateien geladen wurden.
- start_url: Startseite der Anwendung, wenn sie auf einem Gerät (d. h. in installierter Variante) gestartet wird.
- display: Gibt an, wie die Anwendung gestartet werden soll. Mögliche Werte sind fullscreen, standalone, minimal-ui und browser. Von links nach rechts werden mehr und mehr Elemente vom Betriebssystem/Browser bei der laufenden Applikation angezeigt.
Neben der manifest.json-Datei, die jede PWA benötigt, wird speziell für Angular noch eine weitere Datei zur Instrumentierung des Angular Service Workers benötigt. Diese Datei legen wir ebenfalls im Ordner src mit dem Namen ngsw-config.json an. Der Inhalt der Datei ist in Listing 2 zu finden. Während des Build-Prozesses wird diese Datei in eine Datei mit dem Namen ngsw.json überführt und im Build-Ordner abgelegt. Diese dient dann zur eigentlichen Instrumentierung des Angular Service Workers.
Listing 2: Inhalt der Datei src/ngsw-config.json
{ "index": "/index.html", "assetGroups": [ { "name": "app", "installMode": "prefetch", "resources": { "files": [ "/favicon.ico", "/index.html" ], "versionedFiles": [ "/*.bundle.css", "/*.bundle.js", "/*.chunk.js" ] } }, { "name": "assets", "installMode": "lazy", "updateMode": "prefetch", "resources": { "files": [ "/assets/**" ] } } ], "dataGroups": [ { "name": "pokemon", "urls": [ "https://pokemon.co/*" ], "cacheConfig": { "strategy": "performance" } } ] }
Schauen wir uns das Cache-API des Browsers genauer an, da wir es zur Implementierung der Offlinefähigkeit benutzen werden. Generell kann Angular bzw. der Angular Service Worker den Cache auf zwei Arten füllen: Mit statischem und dynamischem Inhalt.
Der statische Inhalt kann über die assetGroups (siehe Listing 2) bestimmt werden. Die Eigenschaft assetGroups ist ein Array aus Konfigurationsobjekten, welche wir uns im Folgenden genauer anschauen wollen:
- name: Freiwählbarer Name für die aktuelle Konfiguration. Praktisch zum Gruppieren verschieden gecachter Inhalte der Anwendung. Der Name ist auch Teil des Cachenamens im Browser, was das Debugging vereinfacht.
- installMode: Gibt an, ob der Cache bei der Installation des Service Workers direkt befüllt werden soll (Modus prefetch) oder erst bei Zugriff auf die jeweilige Datei (Modus lazy). Es empfiehlt sich alle Dateien, die zum Start der Anwendung benötigt werden, via prefetch zu laden, sodass nach dem ersten Laden die Anwendung offline zur Verfügung steht.
- updateMode: Gibt an, wie der Cache aktualisiert werden soll, wenn für eine Datei eine neue Version zur Verfügung steht. Auch hier sind die Werte prefetch und lazy möglich. prefetch gibt an, dass immer die neueste Version der Datei in den Cache geladen wird, sobald diese zur Verfügung steht. Bei URL-basierten Cacheeinträgen (siehe nächste Konfiguration resources) erfolgt immer ein Update. Bei Hash-basierten Dateien erfolgt ein Update bei Änderung des Dateihashes. Bei lazy wird die Prüfung auf eine neue Version erst dann gemacht, wenn auch ein Zugriff auf die Datei im Cache erfolgt. Der eigentliche Update-Zyklus ist dann wie im Modus prefetch.
- resources: Diese Konfiguration gibt explizit an, welche Dateien in den Cache geladen werden soll. Es stellt ein Objekt mit drei verschiedenen Eigenschaften dar:
- files: Eine Liste von Dateien, die in den Cache geladen werden soll. Diese Dateien müssen im Build-Verzeichnis liegen (also dort, wo auch später die ngsw.json abgelegt wird). Für alle Dateien innerhalb dieser Liste wird ein Dateihash gebildet, der für ein einfaches Erkennen sorgt, wenn die Datei auf eine neue Version geprüft werden soll. Die Einträge innerhalb dieser Liste können auch via Globs angegeben werden.
- versionedFiles: Eine Liste von Dateien, die bereits einen Hash im Dateinamen haben, wie beispielsweise alle durch das Angular CLI erstellten. Diese müssen nicht zusätzlich gehasht werden, da bei einer Änderung sich der Name der Datei ebenfalls ändert.
- urls: Liste von externen URLs, die zusätzlich gecacht werden sollen. Bspw. Google-Fonts-Dateien. Für diese kann kein Dateihash berechnet werden, daher werden diese Dateien immer aktualisiert, sobald die Konfiguration sich ändert. Daher sind die anderen zwei Möglichkeiten zu bevorzugen.
Je nach Einstellung wird der Service Worker beim Start der Anwendung oder wenn der Cache nicht gefüllt ist, selbstständig alle definierten Dateien vom Server laden und im Cache ablegen. Ab diesem Moment ist die Anwendung – ohne Daten – bereits offline verfügbar. Über die Dev Tools von Chrome kann im Tab Network der Browser auf offline geschaltet werden. Wird die Anwendung per F5 aktualisiert, wird die Anwendung dennoch geladen, da sie vollständig im Cache des Browsers liegt.
Möchten wir die Antworten unserer API-Anfragen an das Star Wars- oder Pokémon-API im Cache ablegen, müssen wir einen Blick auf das Array dataGroups werfen. Die Konfiguration unterscheidet sich etwas zu den assetGroups:
- name: Freiwählbarer Name, wie den den assetGroups
- urls: Eine Liste von URLs, deren Antwort gecacht werden soll. Globs werden unterstützt.
- version: Möglichkeit, die Daten zu versionieren, was bei Migration oder Anwendungsupdates nützlich sein kann.
- cacheConfig: Objekt zum Bestimmen des Cachingsverhalten einer einzelnen Konfiguration.
- maxSize: Anzahl, wie viele Requests innerhalb der Gruppe gecacht werden soll (bspw. bei Wildcards in der URL)
- maxAge: Angabe, wie lange die Antwort im Cache als gültig angesehen wird. Mögliche Werte sind string-basierte Zeitangaben wie 1m, 4h, 10d (1 Minute, 4 Stunden, 10 Tage).
- strategy: Für welche Art der Verwendung der Cache optimiert werden soll. Es kann aktuell zwischen freshness und performance gewählt werden. Bei freshness wird immer versucht, die Daten vom Server zu laden und bei erfolgreichem Fall im Cache abzulegen. Nur im Offlinefall wird direkt auf den Cache zugegriffen. freshness implementiert also ein Network First-Verhalten. performance implementiert ein Cache First-Verhalten und lädt die Dateien immer aus dem Cache und nur dann vom Server, wenn sie noch nicht im Cache liegen.
- timeout: Wird nur genutzt, wenn die strategy auf freshness gestellt ist und bestimmt, nach welcher Zeit der Request in ein Timeout läuft und auf den Cache zurückgegriffen werden soll.
Zum Zeitpunkt des Schreibens des Artikels existiert für dieses Feature noch keine schriftliche Dokumentation, daher lohnt sich ein Blick in das GitHub-Repository. Innerhalb unseres Beispiels cachen wir nur das Pokémon-API. Als kleine Übung kann der Cache für das Star-Wars-API eingefügt werden.
Als vorletzter Schritt muss in der index.html-Datei im <head> folgender Eintrag hinzugefügt werden, sodass der Browser weiß, unter welchem Namen er das PWA-Manifest findet: <link rel=”manifest” href=”manifest.json”>. Zu guter Letzt müssen wir in unserer module.ts noch das Module ServiceWorkerModule den imports hinzufügen und zwar mit folgendem Ausdruck:
environment.production ? ServiceWorkerModule.register('ngsw-worker.js') : [],
Der Service Worker kann aktuell nur in Angulars Produktionsmodus gestartet werden, da nur hier die angegebene Datei ngsw-worker.js generiert wird. Das erschwert aktuell das Debugging, wird aber mit der Weiterentwicklung des Toolings besser werden. Zudem muss die Anwendung zwingend gebaut und nicht via ng serve gestartet werden. Das heißt, die Anwendung muss via ng build –prod gebaut und über einen Webserver (z.B. node-static oder http-server) gestartet werden. Nach dem Start kann etwas durch die Anwendung geklickt werden. Über die Chrome Dev Tools kann der Browser in den Offlinemodus (Tab Networking) geschaltet werden. Auch nach einem Reload des Browsers wird die Anwendung angezeigt. Zuvor angezeigte Daten sind auch im Offlinemodus verfügbar. So haben wir bereits die Möglichkeit, unsere Anwendung in Teilen offline zunehmen.
Hinweis: Das Angular-CLI bzw. der Angular Service Worker abstrahieren sehr viele Low-Level-APIs. Was so einfach aussah, abstrahiert Themen wie den Service Worker Lifecycle, das Service-Worker-API, das Cache-API, Offlinestrategien oder das Fetch-API. Es gibt also noch viel zu entdecken!
Ich hab’ da was für dich: Push-Nachrichten
Moderne Anwendungen kommen kaum noch ohne aus, und der Benutzer verlässt sich darauf, bei neuen Ereignissen in seiner Anwendung eine Notification zu erhalten. Auch wir wollen die Möglichkeit haben, Push-Nachrichten zu empfangen und eine Notification anzuzeigen. Hier ist auch wichtig, dass eine Push-Nachricht nicht automatisch etwas mit einer dem Benutzer angezeigten Notification zu tun hat. Push-Nachrichten sind generell die Möglichkeit, einer Anwendung eine Nachricht zukommen zu lassen. Die Anwendung hat daraufhin die Möglichkeit, diese Push-Nachricht als Notification dem Benutzer anzuzeigen. Prinzipiell könnte stattdessen auch nur ein Datensatz aktualisiert oder ein interner Prozess angestoßen werden. Was genau gemacht werden soll, wird durch die Payload einer Notification bestimmt.
Zur Umsetzung von Push-Nachrichten mit Notification werden wir uns zweier APIs bedienen: des Push-API und des Notification-API. Beide APIs werden durch den Angular Service Worker abstrahiert, sodass wir nicht direkt mit ihnen in Berührung kommen, sie aber grundlegend für die Funktion verantwortlich sind. Um Push-Nachrichten anzubieten, werden zwei Dinge benötigt:
- Implementierung eines App-Servers zum Verwalten von Push-Subskriptionen und Versenden von Push-Nachrichten.
- Implementierung innerhalb der Clientanwendung.
Erste Implementierungen von Push in Google Chrome haben einen Dienst von Google Cloud Messaging (GCM) bzw. Firebase Cloud Messaging (FCM) benötigt. Leider hat es sich hierbei um ein propritäres Protokoll gehandelt, welches nur für Chrome-basierte Browser zur Verfügung stand. Wollte man Push-Nachrichten bspw. für Firefox implementieren, musste auf den Mozilla Push Service (MPS) zurückgegriffen werden. Für uns Entwickler bedeutet dies, dass wir für beide Services unsere Anwendung registrieren mussten, um Push-Nachrichten zu versenden.
Mittlerweile existiert ein neues, standardisiertes, Protokoll mit dem Namen Web Push, welches von Firefox und Chrome implementiert, von FCM und MPS unterstützt werden und der zukünftige Weg ist, Push-Nachrichten an die Browser zu schicken. Safari und Edge hinken hier leider noch etwas hinterher.
Durch das Web Push Protokoll müssen wir uns nicht mehr für jeden Dienst selbst registrieren, sondern können dies über den neuen Standard VAPID (Voluntary Application Server Identification) erledigen. Für VAPID wird auf unserem App-Server, der die Push-Subskriptionen verwaltet und die Push-Nachrichten verschicken kann, ein Schlüsselpaar bestehend aus Public- und Private-Key erzeugt. Möchte ein Browser sich für Push registrieren, benötigt er den Public-Key des App-Servers. Durch den korrekten Public-Key kann der App-Server eine Verbindung mit FCM oder MPS aufbauen, die nach wie vor für den eigentlichen Versand der Push-Nachricht benötigt werden. Durch VAPID identifizieren wir unseren App-Server bei FCM und MPS, weswegen die Registrierung bei den Diensten entfällt.
Da die Implementierung des App-Servers den Rahmen des Artikels verlässt, liegt eine Beispielimplementierung eines App-Servers im Ordner srcApi bereit, der über npm run push-server gestartet werden kann. Das benötigte Schlüsselpaar liegt exemplarisch bei, kann aber bspw. über eine Website wie den Push Companion oder das Node.js-Modul crypto selbst erzeugt werden.
Der App-Server stellt nach dem Start zwei Web-APIs bereit. Das erste API dient zur Registrierung von Clientanwendungen, sodass der App-Server weiß, an welche Clients er Push-Nachrichten übermitteln kann. Das zweite API ist ein kleines Debug-API, mit dem wir den Versand von Push-Nachrichten an alle subskribierten Clients anstoßen können.
Der dritte Schritt ist das Implementieren von Push-Nachrichten in unserer Anwendung selbst. Dazu ändern wir den Code der root.ts ab, wie in Listing 3 gezeigt.
Listing 3: Ergänzung der Root-Komponente
export class RootComponent implements OnInit { constructor(@Optional() private _swPush: SwPush, private _http: HttpClient) {} public ngOnInit(): void { this.register(); } public register(): void { if (!this._swPush) { return; } const push = this._swPush.requestSubscription({serverPublicKey:''}); Observable.fromPromise(push) .pipe( switchMap(subscription => this._http.post('http://localhost:9090/push/register', subscription)) ) .subscribe(); } }
Zum einen lassen wir uns eine Instanz von SwPush und HttpClient geben. SwPush ist ein Angular Service, der uns Zugriff auf die Push-Funktionalität des Angular Service Workers gewährt. Zum anderen implementieren wir die Methode register(), um via SwPush die Methode requestSubscription() aufzurufen, um den Service Worker am Browser für Push-Nachrichten zu registrieren. Hier wird der zur für VAPID erzeugte Public-Key benötigt. Ist die Registrierung am Browser erfolgreich, erhalten wir Informationen über die Subskription subscription, die wir via http.post() an unser eigenes API schicken. Wie dem Beispiel zu entnehmen ist, läuft das API auf localhost:9090, d. h. dass nur Anwendungen mit Zugriff auf localhost sich für den Empfang von Push-Nachrichten registrieren können. Für ein echtes Szenario muss der App-Server online gehostet werden, sodass er durch die mobilen Apps erreicht werden kann. Und natürlich sollte der Beispielcode in einen schönen, eigenen Angular Service abstrahiert werden.
Ein kleines Manko hat der Angular Service allerdings noch: Die Anwendung hat noch keine Möglichkeit, auf die Push Notifications selbst zu reagieren. Die dahinterliegenden Low-Level-APIs bieten bereits die Möglichkeit, auf den Klick einer Notification zu reagieren oder Daten aus Notifications auszulesen. Diese Möglichkeit wird in einer künftigen Version des Angular Service zur Verfügung stehen. Benötigt man diese Funktionen, muss man selbst die Low-Level-APIs implementieren.
Nach dem Start des App-Servers kann die Anwendung gestartet werden. Via Postman kann ein HTTP POST auf localhost:9090/push/notifyAll abgesetzt werden, was dazu führt, dass Chrome eine Desktop-Notification anzeigen wird. Mit diesen wenigen Schritten haben wir Push Notifications implementiert!
Der Wegweiser: Lighthouse
Google bietet das Tool Lighthouse an, das unsere Anwendung unter anderem auf die eingangs genannten Eigenschaften von PWAs prüft und auch Vorschläge gibt, wie sie verbessert werden können. Das Tool kann als Chrome Extension installiert werden und wird per einfachem Klick auf das Lighthouse-Icon gestartet. Die Prüfung ist vollautomatisch und dauert wenige Sekunden. Lighthouse generiert einen Report, der in Abbildung 2 gezeigt wird. Das Scoring von 100/100 Punkten für PWAs zeigt, dass wir sehr gute Arbeit geleistet haben. Einen Teil der Arbeit hatten wir im Vorfeld bereits erledigt, wie Responsive, und einen weiteren Teil der Arbeit mit dem Artikel durch die Integration des Service Workers und das Anbieten einer Manifest-Datei.
Fazit
Mit Hilfe des Artikels haben wir eine bestehende Angular-Cross-Plattform-Anwendung um weitere Technologien ergänzt, um die Anwendung in Richtung Progressive Web App weiterzuentwickeln. Die Single-Codebase stellt hierbei nicht nur eine Webanwendung bereit, sondern kann, wie eingangs angesprochen, in echte native mobile Apps und echte native Desktopanwendungen verpackt werden, die jeweils auf native APIs zugreifen können, um dem Benutzer eine bestmögliche Integration zu bieten. Mit ein paar wenigen Zeilen konnten wir in diesem Artikel unsere Anwendung um PWA-Technologien erweitern. Via Service Worker haben wir eine einfache Möglichkeit, um die Anwendung mit einfacher Offlinefunktionalität auszustatten und auf Push-Nachrichten reagieren zu können. Dank der Manifest-Datei ist unsere App auch installierbar geworden, was auf Android-Geräten durch ein kleines Banner angezeigt wird (Abb. 3), und das ganz ohne einen App Store! Lighthouse hat uns gezeigt, dass wir ein sehr gutes PWA-Scoring erhalten, somit steht unserer modernen Anwendung kaum noch was im Wege. Unter der Haube bieten die Low-Level-APIs noch viel mehr, schließlich haben wir mit diesem Artikel nur an der Oberfläche gekratzt.
Angular und PWAs auf der BASTA!:
Passend zum Thema dieses Artikels finden Sie auf der BASTA! z. B. die Session „Progressive Web Apps mit Angular: das Web wird nativ(er)“ von Christian Liebel und viele weiter im HTML5 & JavaScript Track sowie den Power Workshops am Montag und Freitag.