Workbox

Maxim Salnikov

Angular GDE

Building an Angular PWA:

NGSW

?

?

?

- or -

+ what's new in PWA

Maxim Salnikov

  • Google Developer Expert in Angular

  • Angular Oslo / PWA Oslo meetups organizer

  • ngVikings /  ngCommunity organizer

Azure Developer Technical Lead at Microsoft

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.

works everywhere*

* but not everything**

natively

** use progressive enhancement strategy

UX advantages?

Smart networking + Offline

Proper app experience

Staying notified

Other cool things

}

Service Worker API

Web App Manifest

Almost 100 new APIs

Badging API

Contact Picker API

Native File System API

Periodic Background Sync API

  • NOT a "cron" in a browser

  • Works only when online

  • The browser is a decision maker

Installation

Stores

Anything else we need?

Create Angular PWA

  • Code service worker manually

  • Use Angular Service Worker (NGSW)

  • Use some PWA libraries

Minimum viable PWA

=

+

Application shell

Web App Manifest

Fast, responsive, mobile-first

Served via HTTPS

Let's build an App shell

My App

  • Define assets

  • Put in the cache

  • Serve from the cache

  • Manage versions

}

Service worker

Logically

Physically

-file(s)

App

Service-worker

Browser/OS

Event-driven worker

Cache

fetch
push
sync

Managing cache

self.addEventListener('install', (event) => {
  
    // Put app's html/js/css to cache

})
self.addEventListener('activate', (event) => {
  
    // Wipe previous version of app files from cache

})

In the real world

  • Can't add opaque responses directly

  • Redirected requests should be managed

  • Always creating a new version of cache and deleting the old one is not optimal

  • Control over cache size is required

  • Cache invalidation for runtime caching is complex

  • ...

Intercepting requests

self.addEventListener('fetch', (event) => {

  if (event.request.url.indexOf('/api') != -1) {
    event.respondWith(
      // Network-First Strategy
    )
  } else {
    event.respondWith(
      // Cache-First Strategy
    )
  }
})

In the real world

  • All kinds of fallbacks needed for the strategies

  • There are more complex strategies like Stale-While-Revalidate

  • Good to have routing

  • Good to have the possibility to provide some extra settings for different resource groups

  • ...

Pros

  • Great flexibility!

 

Cons

  • Great responsibility!

 

  • Implementing complex algorithms

  • Adopting best practices

  • Focusing on YOUR task

  • Following specifications updates

  • Handling edge cases

Tools help with

NGSW

Angular Service Worker

NGSW

Automation

Scaffolding

Building

Serving

Schematics

Angular CLI

NGSW

$ ng add @angular/pwa

Scaffold

$ ng build --prod

Build

$ ng serve

Serve

ngsw-config.json

src/manifest.webmanifest

Updated

What's happened?

Built

ngsw.json

ngsw-worker.js

src/assets/icons/icon-*.png

Scaffolded

src/index.html

src/app/app.module.ts

ngsw-config.json / assetGroups

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







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

Configuration file

  • 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

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

Workbox manifest

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

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

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: [...]
  })
);

Strategies

  • CacheFirst

  • CacheOnly

  • NetworkFirst

  • NetworkOnly

  • StaleWhileRevalidate

Plugins

  • Expiration

  • CacheableResponse

  • BroadcastUpdate

  • BackgroundSync

  • ...your own plugin?

NGSW

  • Variety of strategies

  • Maximum flexible configuration including adding own logic via the plugins

  • Code-free configuration of two strategies

  • Runtime cache versioning

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();
    }

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.

Using workbox-window

import { Workbox } from 'workbox-window'

platformBrowserDynamic().bootstrapModule(AppModule)
  .then( () => {
    if ('serviceWorker' in navigator) {
      const wb = new Workbox('service-worker.js');
      // Event listeners...
      wb.register();
    }
  })

main.ts

$ npm install workbox-window

Was service worker file updated?

wb.addEventListener('installed', event => {
  if (event.isUpdate) {
  // Show "Newer version is available. Refresh?" prompt
  } else {
  // Optionally: show "The app is offline-ready" toast
  }
});

main.ts

workbox.core.skipWaiting()
workbox.core.clientsClaim()

service-worker.js

Must have!

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

Summary

NGSW

Events to inform about app shell updates

Runtime caching based on the strategies

Push notifications helpers

Web App Manifest

Extendable

Application shell

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

  • 2000+ developers

  • Representatives from the browsers, frameworks, libraries

Thank you!

Maxim Salnikov

@webmaxru

Questions?

Maxim Salnikov

@webmaxru

Made with Slides.com