Maxim Salnikov
Angular GDE
How to create an Angular Progressive Web App?
Products from the future
UI Engineer at ForgeRock
Progressive web apps use modern web APIs along with traditional progressive enhancement strategy to create cross-platform web applications.
Flagged
OS
Service Worker API
Web App Manifest
My App
The app was updated.
Refresh?
$ ng add @angular/pwa
$ ng build --prod
{
"hashTable": {
"/favicon.ico": "84161b857f5c547e3699ddffc6d8d",
"/index.html": "64397c08d1f0da35f8e38e05c5512",
...
},
...
}
{
"name": "app",
"installMode": "prefetch",
"resources":
}
{
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
$ ng serve
$ ng serve --prod
# 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.`)
})
[
{
"url": "index.html",
"revision": "34c45cdf166d266929f6b532a8e3869e"
},
{
"url": "favicon.ico",
"revision": "b9aa7c338693424aae99599bec875b5f"
},
...
]
// 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([])
{
"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')
]);
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/service-worker.js')
}
import { register } from 'register-service-worker'
platformBrowserDynamic().bootstrapModule(AppModule)
.then( () => {
register('/service-worker.js', {
})
})
$ npm install register-service-worker
updated (registration) {
// Inform & prompt
}
{
"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
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
})
}
{
"notification": {
}
}
"title": "Very important notification",
"body": "Angular Service Worker is cool!",
"icon": "https://angular.io/assets/logo.png",
"actions": [
{
"action": "gocheck",
"title": "Go and check"
}
],
...
self.addEventListener('push', (event) => {
self.registration.showNotification(...)
})
self.addEventListener('notificationclick', (event) => {
// React on notification actions
})
self.addEventListener('notificationclose', (event) => {
// React on notification closing
})
Maxim Salnikov
@webmaxru
Maxim Salnikov
@webmaxru