NGSW or Workbox?
Maxim Salnikov
Angular GDE
Creating Angular PWA
How to create Angular Progressive Web App?
Using the proper tools
Maxim Salnikov
-
Google Developer Expert in Angular
-
Angular Oslo / PWA Oslo meetups organizer
-
ngVikings conference organizer
Products from the future
UI Engineer at ForgeRock
After all, what is PWA?
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.
#YearOfPWA
Latest updates
Cross-platform?
Browser
Desktop
Mobile
Flagged
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)
-
Go for some PWA libraries
sw-precache
+
=
<script />
Service Worker 101
Wait a sec!
-
1500+ developers
-
Major browsers/frameworks/libs reps
App shell
My App
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
Angular Service Worker
NGSW
Generate a new Angular PWA
$ ng new myPWA --service-worker
-
Angular CLI 1.6-1.7
-
Angular Service Worker 5
"Yesterday"
Generate a new Angular PWA
$ ng new myPWA
-
Angular CLI 6
-
Angular Service Worker 6
-
Web App Manifest
-
@Schematics
Today
$ ng add @angular/pwa
Building Angular PWA
$ ng build --prod
ngsw-worker.js
ngsw.json
dist/
safety-worker.js
assets/manifest.json
Checking the status
https://yourwebsite.com/ngsw/state
NGSW Debug Info:
Driver state: NORMAL ((nominal))
Latest manifest hash: cd4716ff2d3e24f4292010c929ff429d9eeead73
Last update check: 9s215u
=== Version 34c3fd2361735b1330a23c32880640febd059305 ===
Clients: 7eb10c76-d9ed-493a-be12-93f305394a77
=== Version cd4716ff2d3e24f4292010c929ff429d9eeead73 ===
Clients: ee22d69e-37f1-439d-acd3-4f1f366ec8e1
=== Idle Task Queue ===
Last update tick: 4s602u
Last update run: 9s222u
Task queue:
Debug log:
Registering NGSW
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
...
@NgModule({
...
imports: [
...
]
})
export class AppModule { }
ServiceWorkerModule.register('/ngsw-worker.js',
{ enabled: environment.production }),
app.module.ts
NGSW configuration file
src/ngsw-config.json
{
"index": "/index.html",
"assetGroups": [...],
"dataGroups": [...]
}
App shell
assetGroups
{
"name": "app",
"installMode": "prefetch",
"resources": {...}
}
App shell resources
assetGroups / "app" / resources
"resources": {
}
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
],
"files": [
"/favicon.ico",
"/index.html"
],
App shell / on-demand
assetGroups
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {...}
}
App shell / on-demand
assetGroups / "assets" / resources
"resources": {
}
"files": [
"/assets/**"
],
"urls": [
"https://fonts.googleapis.com/**",
"https://fonts.gstatic.com/**"
]
Runtime caching
dataGroups
{
"name": "api-freshness",
"urls": [
"/api/breakingnews/**"
],
}
"cacheConfig": {
"strategy": "freshness",
"maxSize": 10,
"maxAge": "12h",
"timeout": "10s"
}
Runtime caching
dataGroups
{
"name": "api-performance",
"urls": [
"/api/archive/**"
],
}
"cacheConfig": {
"strategy": "performance",
"maxSize": 100,
"maxAge": "365d"
}
Support API versioning
dataGroups
{
"version": 1,
"name": "api-performance",
"urls": [
"/api/**"
],
...
}
{
"version": 2,
"name": "api-performance",
"urls": [
"/api/**"
],
...
}
App version updates
v1
v2
v1
v1
v2
Server
Browser
v2
Notify about updates
import { SwUpdate } from '@angular/service-worker';
constructor(private swUpdate: SwUpdate) {}
this.swUpdate.available.subscribe(event => {
let snackBarRef = this.snackBar
.open('Newer version of the app is available', 'Refresh');
snackBarRef.onAction().subscribe(() => {
window.location.reload()
})
})
updates.component.ts
Push notifications
import { SwPush } from '@angular/service-worker';
constructor(private swPush: SwPush) {}
subscribeToPush() {
this.swPush.requestSubscription({
serverPublicKey: this.VAPID_PUBLIC_KEY
})
.then(pushSubscription => {
// Pass subscription object to backend
})
}
push.component.ts
Push notifications / send
{
"notification": {
}
}
server-side.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"
}
],
...
Kill switch
1. Long way
ng set apps.0.serviceWorker=false
ng build --prod
...deploy
2. Short way
rm dist/ngsw.json
...deploy
Kill switch
3. Proper way
cp dist/safety-worker.js dist/ngsw-worker.js
...deploy
self.addEventListener('install', e => { self.skipWaiting(); });
self.addEventListener('activate', e => {
e.waitUntil(self.clients.claim());
self.registration.unregister().then(
() => { console.log('Unregistered old service worker'); });
});
safety-worker.js
Main available features
App Shell
Runtime Caching
Push Notifications
Smart Updates
Pros
-
Smart defaults go out of the box
-
Essential features are codeless
-
Doing things in Angular way
Cons
-
Intended to play a main service worker's role in your PWA
-
There is no good way to extend the functionality
+
=
# Install the Workbox CLI
$ npm install workbox-cli --global
# Run the wizard to generate a service worker
$ workbox wizard
App shell
Runtime caching
Offline GA
Replay failed requests
Broadcast updates
Build integrations
Workbox 101
module.exports = {
"globDirectory": "dist/",
"globPatterns": [
"**/*.{txt,png,ico,html,js,json,css}"
],
"swDest": "dist/sw.js"
};
workbox-config.js
App shell
module.exports = {
"globDirectory": "dist/ngPwa",
"globPatterns": [
"favicon.ico",
"index.html",
"*.css",
"*.js",
"assets/**/*"
],
"swDest": "dist/ngPwa/sw.js"
};
workbox-config.js
App shell
importScripts("https://storage.googleapis.com/.../workbox-sw.js");
self.__precacheManifest = [
{
"url": "index.html",
"revision": "b3429974cda6f87f84df90b35fc6faa4"
}, ...
].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
sw.js
Generated service worker
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw-default.js')
}
});
main.ts
Registration
Caching and serving
Our own service worker
"assets": [
{
"glob": "**",
"input": "../libraries/",
"output": "./"
}
...
]
.angular-cli.json
$ npm install --save-dev workbox-build
$ workbox copyLibraries libraries/
Our own service worker
const {injectManifest} = require('workbox-build')
const swSrc = 'src/service-worker.js';
const swDest = 'dist/sw-default.js'
injectManifest({
swSrc, swDest, ...
})
.then(({count, size}) => {
console.log(`Generated SW which will
precache ${count} files,
totaling ${size} bytes.`)
})
workbox-build.js
Our own service worker
importScripts("workbox-v3.2.0/workbox-sw.js");
workbox.precaching.precacheAndRoute([])
my-serviceworker.js
// Features code
workbox.routing.registerRoute(
/(http[s]?:\/\/)?([^\/\s]+\/)timeline/,
workbox.strategies.networkFirst()
)
...
Pros
-
Can extend existing service worker
-
Feature-rich
Cons
-
Extra build step needed
Thank you!
Questions?
Angular Progressive Web App: NGSW vs Workbox
By Maxim Salnikov
Angular Progressive Web App: NGSW vs Workbox
It eventually happened: Progressive Web Applications took a worthy place in the modern web landscape, and there is no more need to convince developers why to go for performant, reliable, and engaging apps. Your Angular application is not the exception: adding PWA features is getting it to the next level of user experience. We have at least two very interesting options to get there. First, the native Angular Service Worker (NGSW) by Angular team, super-powered by Angular CLI and some extra ng-pwa-tools. Second, the all new framework-agnostic Workbox library by Google Chrome team. What's easier to set up for your Angular app? What has wider functionality? What's faster and more robust? Let's go exploring, coding and testing! You will have 100% full overview of these two approaches after my session, but the final decision is only yours!
- 3,663