PWA ganz einfach mit Angular Service Worker

09.06.2021 Tom Trapp
Mobile angular typescript foss javascript frontend framework handson tutorial howto

Was ist eine Progressive Web App?

Eine sogenannte Progressiv Web Application, kurz PWA, bezeichnet eine Webseite, die besondere Fähigkeiten und Eigenschaften hat um so nah wie möglich an einer nativ installierten Anwendung zu sein.

Beispielsweise für PWAs sind:

  • offline verfügbar
  • startbar vom Homescreen / Desktop
  • fähig, Push Nachrichten zu senden, obwohl PWA im Hintergrund läuft

Den Support für PWA-fähige Browsers gibt es seit etwa 2018, damals mit der Google Chrome-Version 73. Im Jahr 2021 kündigte Chrome an, sogenannte Chrome Apps bis Juni 2022 komplett abschaffen zu wollen. Eine Chrome App ist zwar keine PWA, aber eher eine paketierte (gebundelte) Web-App. Das Konzept einer PWA ist hingegen aber sehr ähnlich. Google selbst empfahl seither Chrome-Apps in PWAs umzubauen und somit die Migration von bisherigen Chrome-Apps vorzunehmen, da dieser Standard auslaufen werde. Das grosse Ziel war es, mobile Anwendungen durch installierbare PWAs zu ersetzen und so eine hybride, eingleisige Entwicklung und Wartung einer Anwendung zu ermöglichen. Bei Google können PWAs gar direkt im Google Play Store gelistet werden und von dort, analog einer normalen nativen App, installiert werden.

Generell bietet eine PWA gegenüber einer mobilen App zahlreiche Vorteile:

  • einfachere Updates (kein AppStore-Prozess, oÄ.)
  • keine Updates oder Installation nötig
  • suchbar über die Google Search Engine
  • einfache Möglichkeiten Links zu versenden
  • kostengünstigere Entwicklung, da Desktop und Mobile gebündelt

Somit würde man vermuten, dass die PWA-Entwicklung überall aus Kosten- und Wartungsgründen gepushed wird. Leider gilt eine (oder zwei) native Apps aber immer noch als 'Statussymbol'. Was die Zukunft bringt kann leider nicht genau gesagt werden, es wird seit Jahren von dem grossen Durchbruch der PWAs berichtet — der leider bislang ausblieb.

Ein Beispiel eine PWA?

Ein tolles Beispiel, was eine PWA kann ist die offzielle Webpräsenz von Starbucks: app.starbucks.com. Hierbei handelt es sich um den WebShop von Starbucks. Darin besteht die Möglichkeit online direkt Kaffee oder ähnliches zu bestellen und in einer Filiale nach Wahl abzuholen zu können. Jeder kennt das Problem — man ist unterwegs, will schnell etwas bestellen und muss erst mühsam eine sehr grosse, sprich Speicherintensive App über den App- oder Play Store herunterladen um in den Genuss der Anbieters zu kommen.

Berichten zu Folge ist die PWA von Starbucks 99.84 % kleiner in Bezug auf den Speicher-Footprint als die existierende iOS-App, was eine schnelle Kaffeebestellung sehr viel einfacher und angenehmer macht. Nach dem Launch der PWA verzeichnet Starbucks einen grossen Zuwachs an Bestellungen über die PWA, egal ob auf Mobile oder Desktop.

Die Technik hinter eine PWA?

Die zentrale Stelle einer PWA ist ein sogenannter Service Worker, der als Art Proxy zwischen dem Frontend und dem Backend fungiert. Diese Komponente prüft bei jeden Request, ob a) man online ist und b) ob eine neue Version der Page im Backend zur Verfügung steht. Sollte man offline sein oder es keine neue Version geben, liefert der Service Worker die Page sowie JavaScript, CSS und weitere Web-Ressourcen aus dem Cache und die effektiven Daten aus einer Browser-internen IndexDB.

Diese Technik lässt sich nahezu auf alle aktuellen Webpages per JavaScript anwenden, Frameworks wie Angular oÄ. bieten komplette Libraries für die PWA-Funktionalitäten.

Angular PWA Service Worker Theory

PWA in Angular

Wie genau funktioniert nun aber das ganze Konstrukt run um PWA und Service Worker, kurz SW, in Angular?ds

Auch hier bietet Angular eine sehr einfach aber mächtige Möglichkeit, eine PWA zu schaffen, das @angular/pwa-Package. Dieses Package implementiert die komplette ServiceWorker Funktionalität in ein Angular Projekt, wichtig zu wissen ist, dass der Service Worker auch nach schliessen der Page behalten wird. So kann der Browser beim nächsten Aufruf der Angular PWA alle Ressourcen usw. aus dem Cache über den Service Worker beziehen. Die gecached Version wird dann, solange über den Service Worker ausgeliefert, bis der SW merkt, dass es eine neue Version der Anwendung auf den Server gibt. Angular nutzt hierfür eine Manifest-Datei mit dem Namen ngsw.json, dies ins eine generierte Datei und Informationen für den SW. Über dieses Manifest-File erfährt der Service Worker unter anderen vom Updates, es wird bei jedem Refresh der Anwendung abgerufen.

ToDo List als PWA

Nun wollen wir aber einen Deep-Dive in die Praxis machen und unsere altbekannte b-nova-ToDo-Anwendung als PWA umbauen. Hierfür sind nicht viele Schritte notwendig, da Angular das @angular/pwa-Package bietet, welches alle benötigten Dateien, Konfigurationen, usw. liefern.

Zuerst müssten wir uns einen kleinen lokalen HTTP-Server installieren, da die PWA Funktionalität über beispielsweise ng serve nicht automatisch aktiv ist:

npm install http-server -g

Nachdem wir nun den Server vorbereitet haben, fügen wir das PWA Angular Package zu unserer Anwendung hinzu, wie bereits in vergangen Blogposts dieser Serie nutzen wir hier die Angular-CLI:

ng add @angular/pwa --project b-nova-todo-list

Nun ist unsere b-nova-ToDo-Liste ready um als PWA gebaut und gestartet zu werden. Die PWA Funktionalitäten werden erst bei einem produktiven Build aktiviert:

ng build --prod

Zu guter letzte Starten wir unser fertig gebautes Angular Projekt mit dem vorher installierten HTTP-Server:

http-server -p 8080 -c-1 dist/b-nova-todo-list -o

Ist das jetzt schon eine PWA?

Berechtigte Frage, beim Aufruf der Seite stellt man auf den ersten Blick keinerlei Änderungen fest und bekommt nichts von den Vorteilen mit. Schaut man genauer hin, sieht man in der Adressleiste des Browsers (hier Google Chrome) ein kleines neue Icon:

Klicken wir nun hier drauf meldet uns der Browser, dass wir hier eine Anwendung installieren können, dies ist das erste grosse Feature der PWA.

Angular PWA Service Worker Install Icon

Sobald wir die PWA installiert haben, können wir uns das Icon auf den Desktop legen und die Anwendung ohne Browser Overhead ausführen, analog zu einer nativ installierten Anwendung — cool, oder?

Angular PWA Service Worker b-nova To Do PWA

Schaut man noch genauer hin, findet man in den Google Chrome Developer Tools im Tab 'Application' einen Eintrag 'Service Workers'. Hier sehen wir mehr Informationen zu unserem aktuell installierten Service Worker, ausserdem gibt es eine spannende Checkbox 'offline'.

Angular PWA Service Worker Google Chrome Menu

Mittels dieser 'offline'-Checkbox lässt sich die Offline-Funktionalität der PWA simulieren und testen. Wie Sie nun sehen ist unsere ToDo-Anwendung auch im Offline-Betrieb voll funktionsfähig, die Änderungen oder neuen Einträge werden im Local Storage des Browsers gespeichert.

Nun wollen wir aber einen Schritt weiter gehen und stoppen unsere HTTP-Server. Nun ist die Applikation quasi ausgefallen und nicht mehr aufrufbar. Glücklicherweise hilft der Service Worker und auch hier, sowohl die Web-Anwendung im Browser sowie die installierte PWA sind weiterhin, ohne HTTP-Server, weiterhin voll nutzbar!

Service Worker & Rest API

Zu guter letzt wollen wir es noch ein Ticken spannender machen, wir implementieren einen REST-Endpunkt und machen diese offline-fähig. Dafür benötigen wir natürlich erst ein REST-Backend, hier nutzen wir das NPM-Package mock-rest-server:

npm i -D mock-rest-server

Anschliessend starten wir den REST-Server:

node_modules/.bin/mock-rest-server

Und nun müssen wir ihn noch mit initialen Daten befüllen:

curl -X POST -d '{"title":"Awesome news!","body":"Some content."}' http://localhost:3000/v1/articles
curl -X POST -d '{"title":"Awesome news!","body":"Some more content."}' http://localhost:3000/v1/articles

Rest API aufrufen

Der Einfachheit halber implementieren wir den Aufruf des Restendpunktes direkt in der Komponente:

Im sogenannten ngsw-config.json File kann der ServiceWorker konfiguriert werden.

#to-dos-component.ts

  data:any[] = [];
  
  ...
  
  ngOnInit(): void {
    this.toDos=this.toDoService.getToDos();
    this.http.get<any[]>("http://localhost:3000/v1/articles")
    .subscribe((fetchData: any[]) => {
      this.data = fetchData;
    })
  }

Hier legen wir uns zuerst ein Array an und befüllen es dann in der ngOnInit Methode. Wie wir in den Angular Profi Tipps gelernt haben sollte man hier eine Servicearchitektur mit dedizierten Model-Klassen verwenden.

Nun müssen wir die abgefragten Daten noch in der View ausgeben, hierfür fügen wir einfach folgende Zeile ans Ende des Markups an:

#to-dos-component.html

<br><br>
    <div *ngFor="let item of data">
      <p>{{item.title}} | {{item.body}}</p>
    </div>
</div>

Starten wir unsere Applikation nun wieder als PWA wie oben beschrieben sehen wir unseren Beispielcontent auf der Seite.

Schalten wir nun unsere Rest-Server ab oder simulieren einen Offline Betrieb der PWA bekommen wir Fehler beim Aufruf unseres Restendpunktes und die Daten fehlen in der Anzeige.

Service Worker konfigurieren

Diesen Zustand wollen wir aber nicht, der Service Worker sollte die letzte gecachede Version der Daten nutzen, bevor der REST-Endpunkt ausgefallen ist bzw. wir offline gegangen sind.

Dies können wir mit einer sogenannten dataGroups-Konfiguration im ngsw-config.json erreichen. Eine dataGroups widerspiegelt hier eine nicht von Angular verwaltete Resource wie z.B. unser REST-Endpunkt.

Folgende Definition fügen wir ans Ende des JSON-Objekts ein:

,"dataGroups": [
    {
      "name": "articles",
      "urls": ["/v1/articles"],
      "cacheConfig": {
        "maxAge": "20s",
        "maxSize": 5
      }
    }
  ]

Hiermit sagen wir dem Service Worker, dass die URL /v1/articles für maximal 20 Sekunden gecached werden soll. Dies bedeutet nun, dass nur alle 20 Sekunden ein Call an den REST-Endpunkt gemacht wird und nicht gar wie vorher, bei jedem Aufruf der Seite.

Führen wir nun wieder einen produktiven Build aus und starten unsere HTTP-Server sehen wir ganz normal die Daten. Ein Blick ins Network Tab zeigt, dass die Daten erst nach Ablauf von der 20 Sekunden Cache-Dauer neu abgerufen werden.

Angular PWA Service Worker Network Calls

Das kleine Zahlrad an einem Request sagt aus, dass der Call effektiv gegen aussen und nicht gegen den Service Worker geht.

Stoppen wir nun unseren Rest Endpunkt und symbolisieren einen Ausfall bemerken wir den Ausfall erst nach Ablauf der 20 Sekunden und erneutem Laden in der Applikation.

Abschliessend kann man sagen,...

dass PWAs super sind! 😀

Durch den Einsatz einer Progressiv Web App in Kombination mit Angular lässt sich eine Website oder Anwendung nahtlos auf allen Endgeräten präsentieren und nutzen. Auch auf den zweiten Blick gibt es kaum Nachteile oder Gründe dagegen. Sie haben eine Anwendung oder eine Website, welche Sie als PWA modernisieren und ausliefern wollen? Kontaktieren Sie uns. Stay tuned!

Tom Trapp - problem solver, innovator, athlete. Tom prefers to work on modern software all day long and attaches great importance to objectively clean, lean code.