Maxim Salnikov
Angular GDE
How to create an Angular Progressive Web App?
Azure Developer Technical Lead at Microsoft
Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.
* but not everything**
** use progressive enhancement strategy
Service Worker API
Web App Manifest
My App
The app was updated.
Refresh?
App
Service-worker
Browser/OS
Event-driven worker
Cache
fetch
push
sync
self.addEventListener('install', (event) => {
// Put app's html/js/css to cache
})
self.addEventListener('activate', (event) => {
// Wipe previous version of app files from cache
})
self.addEventListener('fetch', (event) => {
if (event.request.url.indexOf('/api') != -1) {
event.respondWith(
// Network-First Strategy
)
} else {
event.respondWith(
// Cache-First Strategy
)
}
})
$ ng add @angular/pwa
$ ng build --prod
{
"hashTable": {
"/favicon.ico": "84161b857f5c547e3699ddffc6d8d",
"/index.html": "64397c08d1f0da35f8e38e05c5512",
...
},
...
}
{
"name": "app",
"installMode": "prefetch",
"resources":
}
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
# Installing the Workbox Node module
$ npm install workbox-build --save-dev
// 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.`)
})
// 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'
}
// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');
// Precaching and setting up the routing
workbox.precaching.precacheAndRoute([])
[
{
"url": "index.html",
"revision": "34c45cdf166d266929f6b532a8e3869e"
},
{
"url": "favicon.ico",
"revision": "b9aa7c338693424aae99599bec875b5f"
},
...
]
{
"scripts": {
"build-prod": "ng build --prod &&
node workbox-build-inject.js"
}
}
A new version of the app is available. Click to refresh.
import { SwUpdate } from '@angular/service-worker';
constructor(updates: SwUpdate) {}
this.updates.available.subscribe(event => {
})
if (confirm(`New Version is available! OK to refresh`)) {
window.location.reload();
}
{
"appData": {
"changelog": "New version: Dinosaur pic was added!"
}
}
let changelog = event.available.appData['changelog']
let message = `${changelog} Click to refresh.`
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
});
workbox.precaching.addPlugins([
new workbox.broadcastUpdate.Plugin('app-shell')
]);
import { Workbox } from 'workbox-window'
platformBrowserDynamic().bootstrapModule(AppModule)
.then( () => {
if ('serviceWorker' in navigator) {
const wb = new Workbox('service-worker.js');
// Event listeners...
wb.register();
}
})
$ npm install workbox-window
wb.addEventListener('installed', event => {
if (event.isUpdate) {
// Show "Newer version is available. Refresh?" prompt
} else {
// Optionally: show "The app is offline-ready" toast
}
});
workbox.core.skipWaiting()
workbox.core.clientsClaim()
{
"name": "api-freshness",
"urls": [
"/api/breakingnews/**"
],
}
"cacheConfig": {
"strategy": "freshness",
"maxSize": 10,
"maxAge": "12h",
"timeout": "10s"
}
{
"name": "api-performance",
"urls": [
"/api/archive/**"
],
}
"cacheConfig": {
"strategy": "performance",
"maxSize": 100,
"maxAge": "365d"
}
{
"version": 1,
"name": "api-performance",
"urls": [
"/api/**"
],
...
}
{
"version": 2,
"name": "api-performance",
"urls": [
"/api/**"
],
...
}
workbox.routing.registerRoute(
new RegExp('/app/v2/'),
workbox.strategies.networkFirst()
);
workbox.routing.registerRoute(
new RegExp('/images/'),
workbox.strategies.cacheFirst({
plugins: [...]
})
);
Maxim Salnikov
@webmaxru
Maxim Salnikov
@webmaxru