Workbox

Maxim Salnikov

Angular GDE

Building an Angular PWA:

NGSW

?

?

?

- or -

How to create an Angular Progressive Web App?

Using the appropriate method

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular Oslo / PWA Oslo meetups organizer

  • ngVikings /  ngCommunity organizer

Products from the future

UI Engineer at ForgeRock

What is PWA at all?

Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.

These apps work everywhere and provide several features that give them the same user experience advantages as native apps.

Cross-platform?

Browser

Desktop

Mobile

Flagged

OS

#YearOfPWA

UX advantages?

Smart networking + Offline

Proper app experience

Staying notified

Other cool things

}

Service Worker API

Web App Manifest

Create Angular PWA

  • Code service worker manually

  • Use Angular Service Worker (NGSW)

  • Use some PWA libraries

sw-precache

Minimum viable PWA

NGSW

=

+

Application shell

Web App Manifest

Fast, responsive, mobile-first

Served via HTTPS

Let's build an App shell

My App

  • Pick only the files we need

  • Create the list of files and their hashes

  • First load: put these files into the Cache Storage

  • Next loads: serve them from Cache Storage

  • If some files were updated (hashes comparison) put their new versions into the Cache Storage and remove old ones *

  • On the n+1 load - serve the updated  files

The app was updated.

Refresh?

1

2

3

Angular Service Worker

NGSW

Automation

Scaffolding

Building

Serving

Schematics

Angular CLI

NGSW

$ ng add @angular/pwa

Scaffold

  • Add service worker registration code to the root module

  • Generate default service worker configuration file

  • Generate and link default Web App Manifest

  • Generate default icons set

  • Enable build support in Angular CLI config

$ ng build --prod

Build

ngsw.json

ngsw-worker.js

dist/project-name

1

2

  • Builds service worker manifest based on configuration file

  • Copies Angular Service Worker and safety workers

NGSW manifest

{
    "hashTable": {
        "/favicon.ico": "84161b857f5c547e3699ddffc6d8d",
        "/index.html": "64397c08d1f0da35f8e38e05c5512",
        ...
    },
    ...
}

ngsw-config.json / assetGroups

{
    "name": "app",
    "installMode": "prefetch",
    "resources":







}
                {    
        "files": [
            "/favicon.ico",
            "/index.html",
            "/*.css",
            "/*.js"
        ]
    }

Configuration file

Serve (dev)

$ ng serve

Static dev webserver

  • serve

  • superstatic

  • lite-server

$ ng serve --prod
  • Application shell

  • Runtime caching

  • Replaying failed network requests

  • Offline Google Analytics

  • Broadcasting updates

Have our own service worker!

Working modes

  • Workbox CLI

  • Webpack plugin

  • Node module

# Installing the Workbox Node module
$ npm install workbox-build --save-dev

Build script

// We will use injectManifest mode
const {injectManifest} = require('workbox-build')

// Sample configuration with the basic options
var workboxConfig = {...}

// Calling the method and output the result
injectManifest(workboxConfig).then(({count, size}) => {
    console.log(`Generated ${workboxConfig.swDest},
    which will precache ${count} files, ${size} bytes.`)
})

workbox-build-inject.js

Workbox manifest

[
  {
    "url": "index.html",
    "revision": "34c45cdf166d266929f6b532a8e3869e"
  },
  {
    "url": "favicon.ico",
    "revision": "b9aa7c338693424aae99599bec875b5f"
  },
  ...
]

Build script configuration

// Sample configuration with the basic options
var workboxConfig = {
  globDirectory: 'dist/angular-pwa/',
  globPatterns: [
    '**/*.{txt,png,ico,html,js,json,css}'
  ],
  swSrc: 'src/service-worker.js',
  swDest: 'dist/angular-pwa/service-worker.js'
}

workbox-build-inject.js

Source service worker

// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/workbox-sw.js');

// Precaching and setting up the routing
workbox.precaching.precacheAndRoute([])

src/service-worker.js

1

2

Build flow integration

{
  "scripts": {
    "build-prod": "ng build --prod &&
                   node workbox-build-inject.js"
  }
}

package.json

NGSW

  • Convenient build module

  • Having our own service worker and extending it by Workbox modules

  • One-liner to start

  • Seamless integration

  • Smart defaults

Better app update UX

App version updates

v1

v2

v1

v1

v2

Deployed

Displayed

v2

A new version of the app is available. Click to refresh.

SwUpdate service

import { SwUpdate } from '@angular/service-worker';
constructor(updates: SwUpdate) {}
this.updates.available.subscribe(event => {




})

updates.component.ts

    if (confirm(`New Version is available! OK to refresh`)) {
            window.location.reload();
    }

3

Hint: Provide a version description

{
  "appData": {
    "changelog": "New version: Dinosaur pic was added!"
  }
}
let changelog = event.available.appData['changelog']
let message = `${changelog} Click to refresh.`

ngsw-config.json

updates.component.ts

New version: Dinosaur pic was added! Click to refresh.

const updateChannel = new BroadcastChannel('app-shell');

updateChannel.addEventListener('message', event => {
    // Inform about the new version & prompt to reload
});

Option #1: BroadcastChannel

updates.component.ts

workbox.precaching.addPlugins([
    new workbox.broadcastUpdate.Plugin('app-shell')
]);

src/service-worker.js

3

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/service-worker.js')
}

Option #2: Service worker lifecycle

index.html

Requirements

  • Feature detection

  • Registration after app fully loaded and UI rendered

  • Hook into service worker lifecycle update event

  • Was the service worker updated?

  • Was the app itself updated?

register-service-worker

import { register } from 'register-service-worker'

platformBrowserDynamic().bootstrapModule(AppModule)
  .then( () => {
      register('/service-worker.js', {




      })
  })

main.ts

$ npm install register-service-worker
          updated (registration) {
              // Inform & prompt
          }

3

NGSW

  • Possibility to use broadcastUpdate plugin also for receiving runtime caching updates 

  • Angular-style coding: services, DI, observables

  • Passing version info to display in the notification

Runtime caching

Configuring strategies

ngsw-config.json / dataGroups

{
    "name": "api-freshness",
    "urls": [
      "/api/breakingnews/**"
    ],






}
    "cacheConfig": {
      "strategy": "freshness",
      "maxSize": 10,
      "maxAge": "12h",
      "timeout": "10s"
    }

Configuring strategies

{
    "name": "api-performance",
    "urls": [
      "/api/archive/**"
    ],






}
    "cacheConfig": {
      "strategy": "performance",
      "maxSize": 100,
      "maxAge": "365d"
    }

ngsw-config.json / dataGroups

Hint: Support API versioning

{
    "version": 1,
    "name": "api-performance",
    "urls": [
      "/api/**"
    ],
    ...
}
{
    "version": 2,
    "name": "api-performance",
    "urls": [
      "/api/**"
    ],
    ...
}

ngsw-config.json / dataGroups

Strategies and plugins

workbox.routing.registerRoute(
  new RegExp('/app/v2/'),
  workbox.strategies.networkFirst()
);

src/service-worker.js

workbox.routing.registerRoute(
  new RegExp('/images/'),
  workbox.strategies.cacheFirst({
    plugins: [...]
  })
);

NGSW

  • Variety of strategies

  • Maximum flexible configuration including adding own logic via the plugins

  • Code-free configuration of two strategies

  • Runtime cache versioning

Summary

NGSW

  • Easy to start

  • Seamless integration with Angular

  • Coding-free basic features

  • Angular-friendly approach

Add -> Configure

Get what's included

  • Framework-agnostic

  • Rich functionality

  • Maximum flexible configuration

  • Full power of our own service worker

Setup -> Configure -> Code

Get what you want

Thank you!

Maxim Salnikov

@webmaxru

Push notifications

Subscription

import { SwPush } from '@angular/service-worker';
constructor(push: SwPush) {}
subscribeToPush() {
  this.push.requestSubscription({
    serverPublicKey: this.VAPID_PUBLIC_KEY
  })
    .then(pushSubscription => {
      // Pass subscription object to the backend
    })
}

push.component.ts

Sending: following convention

{
  "notification": {










  }
}

backend.js / sendNotification payload

    "title": "Very important notification",
    "body": "Angular Service Worker is cool!",
    "icon": "https://angular.io/assets/logo.png",
    "actions": [
      {
        "action": "gocheck",
        "title": "Go and check"
      }
    ],
    ...

Notifications handling

self.addEventListener('push', (event) => {
    self.registration.showNotification(...)
})

src/service-worker.js

self.addEventListener('notificationclick', (event) => {
    // React on notification actions
})
self.addEventListener('notificationclose', (event) => {
    // React on notification closing
})

NGSW

  • Full power and flexibility of Web Push specification because of having our own service worker

  • Convenient shortcut for the subscription

  • Convention-based automatic notifications displaying

  • [Soon] Notification clicks handling

Summary

NGSW

  • Easy to start

  • Seamless integration with Angular

  • Coding-free basic features

  • Angular-friendly approach

Add -> Configure

Get what's included

  • Framework-agnostic

  • Rich functionality

  • Maximum flexible configuration

  • Full power of our own service worker

Setup -> Configure -> Code

Get what you want

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

Made with Slides.com