Kolumne: Die Angular-Abenteuer

Von null auf Progressive Web App in einem Schritt mit Angular
Keine Kommentare

Mit dem neuen Paket @angular/pwa lässt sich eine klassische Angular-Anwendung augenblicklich in eine offlinefähige und installierbare Progressive Web App umwandeln. Das ist dermaßen einfach, dass es fast keinen Grund gibt, auf diese Vorteile zu verzichten.

2018 ist das Jahr der Progressive Web Apps (PWAs), alle wichtigen Browseranbieter sind nun im Boot. Die Vorzüge von Webanwendungen lassen sich mit jenen ihrer nativen Gegenstücke kombinieren. Eine einfache Auslieferung übers Web gepaart mit Offlinefähigkeit und einem raschen Start über den Home Screen sind nur einige der neuen Möglichkeiten. Abbildung 1 zeigt ein Beispiel für solch eine Anwendung, die gerade eine Push Notification empfängt – eine weitere Möglichkeit von PWAs.

Auch wenn die dafür geschaffenen Browser-APIs vom Prinzip her simpel sind: Handlich sind sie nicht gerade. Deswegen bieten sich darauf aufbauende Bibliotheken an. In dieser Ausgabe möchte ich eine dieser Bibliotheken vorstellen. Sie nennt sich @angular/pwa und ist eine Rundum-sorglos-Lösung des Angular-Teams. Dank Scaffolding und der Bibliothek @angular/service-worker lässt sich damit im Handumdrehen eine normale Angular-App in eine Progressive Web App verwandeln. Den Quellcode dafür gibt es wie immer in meinem Repository.

Abb. 1: Installierte Progressive Web App mit Push Notification

Service Worker

Das Herzstück von Progressive Web Apps ist der Service Worker. Dabei handelt es sich um einen Hintergrundprozess, der von einer Webanwendung im Browser installiert wird und danach autark – also auch ohne Webanwendung – läuft. Dieser Hintergrundprozess ist jedoch nicht ständig am Arbeiten, sondern reagiert lediglich auf Ereignisse. Er nimmt zum Beispiel Push Notification in Empfang oder fängt HTTP-Anfragen ab (Abb. 2).

Abb. 2: Service Worker

Nach dem Abfangen einer HTTP-Nachricht kann der Service Worker entscheiden, wie er darauf antworten möchte. Beispielsweise kann er mit Daten aus dem Cache antworten oder aktuelle Daten aus dem Internet laden. Somit lassen sich verschiedene Caching-Strategien für den Offlinebetrieb implementieren. Einen durchdachten Überblick dazu findet sich im „Offline Cookbook“ von Jake Archibald aus dem Google-Chrome-Team.

Im Gegensatz zum normalen HTTP-Cache hat der Entwickler das Verhalten des Service Workers voll unter Kontrolle und kann somit bestimmen, was unter welchen Umständen von wo geladen wird. Wichtig ist auch, dass der Service Worker der Same-Origin Policy unterliegt. Das heißt, er darf nur für die Origin, von der er installiert wurde, tätig werden. Für zusätzliche Sicherheit sorgt auch, dass Service Worker nur über HTTPS ausgeliefert werden dürfen. Eine Ausnahme ist localhost, um lokal zu testen.

Web App Manifest

Damit sich die Anwendung auch auf dem Home Screen installieren lässt, muss sie ein Web App Manifest anbieten. Dabei handelt es sich um eine JSON-Datei mit Metadaten wie dem Namen und dem Icon, mit denen die Anwendung auf dem Home Screen auftauchen soll (Listing 1).

{
  "name": "flight-app",
  "short_name": "flight-app",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    […]
  ]
}

Um festzulegen, wie sich die Anwendung nach dem Start über den Home Screen zu präsentieren hat, setzt man die Eigenschaft display ein. Der hier verwendete Wert standalone sorgt dafür, dass keine Adresszeile erscheint und die Anwendung deswegen wie eine native aussieht (Abb. 1).

Damit der Browser das Web App Manifest entdeckt, muss die Anwendung darauf verweisen. Das erfolgt über einen link-Tag: <link rel=“manifest“ href=“manifest.json“>.

Mobile Browser wie Chrome für Android schlagen sogar aktiv vor, häufig benutzte Web-Anwendung am Home Screen zu installieren, wenn ein Manifest vorliegt und Service Worker zum Einsatz kommen. Ansonsten hat man jederzeit die Möglichkeit, die Anwendung über einen Eintrag im Menü des Browsers zu installieren.

Aber auch Desktopbrowser werden künftig das Manifest stärker berücksichtigen. Beispielsweise bietet Chrome heute schon unter chrome://flags/#enable-desktop-pwas eine Einstellung, die einen Menüeintrag zum lokalen Installieren einer PWA freischaltet. Außerdem erlaubt Googles Browser auch das aktive Vorschlagen einer lokalen Installation bei häufig besuchten Seiten.

Upgrade einer Angular-Anwendung zu einer PWA

Jede Webanwendung kann sehr einfach von den vorgestellten Konzepten profitieren. Offlinefähigkeit bedeutet nämlich auch, dass die Anwendung selbst bei einer schlechten Datenverbindung schnell lädt und dank einer Installation auf dem Homescreen muss man sich nicht an ihre Adresse erinnern. Seit dem Angular CLI 6 lässt sich eine bestehende Angular-Anwendung besonders einfach in eine PWA verwandeln. Dazu ist lediglich eine Anweisung notwendig: ng add @angular/pwa. Diese neue Option richtet in der aktuellen Anwendung ein Web App Manifest samt beispielhafter Icons ein und installiert das Paket @angular/service-worker. Letzteres importiert der Automatismus auch in das AppModule der Anwendung. Somit erhält die Anwendung einen vom Angular-Team bereitgestellten Service Worker, der sich ohne weiteres Zutun um Caching und Push Notifications kümmert. Er lässt sich über die Datei ngsw-config.json konfigurieren, für die die CLI ebenfalls ein Grundgerüst generiert. Ein Beispiel dafür findet sich in Listing 2.

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.ico",
        "/index.html",
        "/*.css",
        "/*.js"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }]
}

Diese Konfigurationsdatei definiert zwei Asset-Groups mit zu cachenden Dateien. Die Eigenschaft installMode stattet diese beiden Gruppen mit dem gewünschten Caching-Verhalten aus. Mit lazy legt sie zum Beispiel fest, dass die Dateien erst bei Bedarf, also beim ersten Zugriff, im Cache landen. Mit prefetch erfolgt das Caching hingegen schon beim Anwendungsstart.

Die beiden gleichen Strategien lassen sich auch für das Aktualisieren der Dateien im Cache mit updateMode festlegen.

Angular PWA testen

Da sich Debugging und Caching bekannterweise nicht gut miteinander vertragen, funktioniert der Angular Service Worker nur in Production Builds. Ein solcher kann über die CLI angefordert werden: ng build –prod.

Zum Ausprobieren braucht man noch einen Webserver, der die ausgelieferten Dateien nicht (!) verändert. Manche für die Entwicklung geschaffenen Webserver wie Live Server machen aber genau das: Sie fügen ein Script in die HTML-Dateien ein, um dem Browser nach einer Dateiänderung einen Reload-Befehl zu senden. Diese sind hier nicht geeignet, weil @angular/service-worker auf einen Caching-Fehler schließt und den Dienst verweigert, sobald eine Datei einen anderen Hash-Code als bei der Kompilierung aufweist.

JavaScript Days 2019

JavaScript Testing in der Praxis (Teil 1 + 2)

mit Dominik Ehrenberg (Crosscan) und Sebastian Springer (MaibornWolff)

Fortgeschrittene schwarze Magie in TypeScript

mit Peter Kröner (‚Webtechnologie-Erklärbär‘)

Bedenkenlos kann hier beispielsweise die Node.js-basierte Lösung HTTP-Server zum Einsatz kommen. Ein npm install -g http-server lädt ihn herunter und anschließend kann man ihn im Ausgabeverzeichnis der Anwendung starten:

 
cd dist
cd anwendungs-name
http-server -o

Um sich zu vergewissern, dass der Service Worker läuft, reicht ein Blick in die Developer Tools von Chrome: Unter Application | Service Worker findet man aktuelle Zustandsinformationen. Etwas aufregender ist hingegen ein Reload der Seite nach dem Beenden des Webservers. Erscheint sie dann noch immer im Browser, funktioniert der Service Worker.

Update

Stehen serverseitig neue Programmdateien zur Verfügung, muss eine PWA die Möglichkeit bieten, den Cache damit zu aktualisieren. Hierzu kommt @angular/service-worker mit einem SwUpdate-Service zum Einsatz. Dieser bietet eine Methode checkForUpdate an, um auf Updates zu prüfen. Hat sie ein solches entdeckt, löst sie ein Ereignis aus. Danach kann die Anwendung mit einer weiteren Methode die neue Programmversion herunterladen.

Um das Update zu aktivieren, gilt es, das Browserfenster zu aktualisieren. Das ist aber keine Einschränkung von Angular, sondern das übliche Verhalten von Browsern. Ein Beispiel dafür findet sich in Listing 3.

[...]
export class AppComponent {

  constructor(
    private snackBar: MatSnackBar,
    private swUpdate: SwUpdate, […]) { 

    […]

    this.setupPush();
  }

  setupUpdates() {
    this.swUpdate.available.subscribe(u => {
      // Update wurde entdeckt

      // Update herunterladen
      this.swUpdate.activateUpdate().then(e => {
        // Update wurde heruntergeladen

        const message = 'Application has been updated';
        const action = 'Ok, Reload!';

        // Benutzer auf Update hinweisen und Seite neu laden
        this.snackBar.open(message, action).onAction().subscribe(
          () => location.reload()
        );
      });
    });

    // Auf Updates prüfen
    this.swUpdate.checkForUpdate();
  }

  [...]
}

Das Ereignis available kommt zur Ausführung, sobald ein Update entdeckt wird. Die asynchrone Methode activateUpdate lädt es herunter. Danach informiert die Snack-Bar den Benutzer über das Update. Nach einer Bestätigung erfolgt der notwendige Reload programmatisch.

Push Notification

Eine weitere Eigenschaft, die sich PWAs von ihren nativen Gegenstücken ausborgen, sind Push Notifications. Um solche Nachrichten über den Service Worker zu empfangen, selbst wenn die Anwendung gerade nicht ausgeführt wird, muss sie sich zunächst bei einem online verfügbaren Push Service anmelden (Abb. 3).

Abb. 3: Funktionsweise von Push Notifications im Browser

Welcher Push Service verwendet wird, kann die Anwendung jedoch nicht entscheiden. Das bestimmt der Browser, der natürlich mit einer Implementierung seines jeweiligen Anbieters vorliebnimmt.

Nach der Anmeldung erhält die PWA ein Subscription-Objekt mit einem URL samt ID, die die aktuelle Anmeldung repräsentiert. Dieses Objekt sendet der Browser zum Backend der Wahl, welches nun zur Benachrichtigung des Browsers Informationen an den darin verstauten URL sendet. Dahinter verbirgt sich der Push Service, der den Service Worker im Browser benachrichtigt.

Durch dieses Verfahren muss jeder Browser nur mit einem einzigen Push Service in Verbindung bleiben. Um Missbrauch zu verhindern, sind die einzelnen Nachrichten verschlüsselt und mit einem signierten Token versehen (Kasten: „Push und Security“).

Push und Security

Um zu beweisen, dass die einzelnen Push Notifications vom Backend der PWA kommen, inkludiert dieses ein signiertes Token in alle Nachrichten. Hierfür generiert das Backend ein Schlüsselpaar, dessen öffentlichen Schlüssel die PWA bei der Anmeldung an den Push Service weiterreicht. Der Push Service kann somit die Herkunft der Push Notifications verifizieren. Damit der Push Service die Nachrichten nicht lesen kann, generiert der Browser ein weiteres Schlüsselpaar. Dessen öffentlichen Schlüssel packt er in das Subscription-Objekt, das die PWA an das Backend sendet. Das Backend kann nun jede versendete Push Notification damit verschlüsseln.

Push Notification und @angular/service-worker

Für die Anmeldung bei einem Push Service bietet @angular/service-worker die Klasse SwPush an. Ihre Methode requestSubscription meldet die PWA beim Push Service des Browseranbieters an (Listing 4).

export class AppComponent {

  constructor(
    private snackBar: MatSnackBar,
    private swUpdate: SwUpdate,
    private swPush: SwPush) { 

    [...]
    this.setupUpdates();
    this.setupPush();
  }

  [...]

  setupPush() {

    const key = 'BBc7Bb5f5...';

    this.swPush.requestSubscription({
      serverPublicKey: key
    })
    .then(sub => {
      console.debug('Push Subscription', JSON.stringify(sub) );
    },
    err => {
      console.error('error registering for push', err);
    });
  }
}

Bei der Anmeldung übersendet requestSubscription einen vom Backend stammenden öffentlichen Schlüssel an den Push Service. Als Antwort wird ein Subscription-Objekt geliefert, das die PWA nun an das eigene Backend senden würde. Zur Vereinfachung wird es hier lediglich auf der Konsole ausgegeben.

Um dennoch das Versenden einer Push Nachricht zu testen, liegt dem Quellcode ein Node.js-Skript send-push.js bei. Trägt man dort das Subscription-Objekt aus der Konsole ein, versendet es eine Push Notification an die PWA. Möchte sich die PWA beim Eintreffen solcher Nachrichten informieren lassen, kann sie das von SwPush angebotene Ereignis messages konsumieren:

this.swPush.messages.subscribe(push => {
console.debug('received push message', push);
});

Fazit und Ausblick

Dank @angular/pwa können Angular-Anwendungen augenblicklich die Vorteile von PWAs nutzen: performanter Programmstart über den Home Screen, Offlinefähigkeit und Push Notifications. Dank Scaffolding und ng add gestaltet sich das Einbinden simpel. Entsprechend gibt es keinen Grund, diese Möglichkeit nicht zu nutzen.

Da die Anwendung nun prinzipiell offlinefähig ist, stellt sich die Frage, ob man auch Daten lokal vorhält. Außerdem könnte man auch im Offlinemodus Benutzereingaben entgegennehmen und in einer Browserdatenbank verstauen. Mit der IndexedDB weisen beispielsweise alle modernen Browser eine Möglichkeit auf, größere Datenmengen lokal zu verwalten. Dank Bibliotheken wie Dexie.js ist der Zugriff auf die IndexedDB ein Kinderspiel.

Kein Kinderspiel sind allerdings die Synchronisation der lokalen Daten mit dem Server sowie die Behandlung von Konflikten, die auftreten, wenn mehrere Benutzer offline dieselben Daten modifizieren. Außerdem muss man sich um die unterschiedlichen Quoten kümmern, die einzelne Browser für lokale Daten aufweisen. Das alles ist immer anwendungsspezifisch, weswegen Bibliotheken hier auch nur bedingt helfen können.

Während die in diesem Artikel beschriebenen Aspekte ohne große Verrenkungen so gut wie immer zusätzlichen Nutzen bringen, ist für die zweite Ausbaustufe mit lokale Daten zwischen Aufwand und Nutzen abzuwägen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -