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
Building an Angular PWA: Angular Service Worker or Workbox?
By Maxim Salnikov
Building an Angular PWA: Angular Service Worker or Workbox?
There is no doubt that 2018 is the #YearOfPWA. It’s the year when Progressive Web Apps will get the really broad adoption and recognition by all the involved parties: browser vendors (finally, all the major ones), developers, users. Angular developers are lucky to have some really nice options to automate their PWA’s main functionality exposed by Service Worker API. The first option is 100% native to the Angular and created by the Angular team: Angular Service Worker. The second one is a framework-agnostic library called Workbox. Both approaches are robust, convenient and unique! Let’s go through the main features of PWA implemented using NGSW vs Workbox and the resulting application lifecycle management. After the session, everyone will give their own answer on what’s easier to start with, which library is simpler to use, which resulting PWA is more convenient to maintain.
- 5,866