Maxim Salnikov
Angular GDE
How to create 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
Service Worker
API
Web App Manifest
<script />
My App
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 new myPWA --service-worker
$ ng new myPWA
$ ng add @angular/pwa
$ ng build --prod
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:
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment';
...
@NgModule({
...
imports: [
...
]
})
export class AppModule { }
ServiceWorkerModule.register('/ngsw-worker.js',
{ enabled: environment.production }),
{
"index": "/index.html",
"assetGroups": [...],
"dataGroups": [...]
}
{
"name": "app",
"installMode": "prefetch",
"resources": {...}
}
"resources": {
}
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
],
"files": [
"/favicon.ico",
"/index.html"
],
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {...}
}
"resources": {
}
"files": [
"/assets/**"
],
"urls": [
"https://fonts.googleapis.com/**",
"https://fonts.gstatic.com/**"
]
{
"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/**"
],
...
}
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()
})
})
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
})
}
{
"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"
}
],
...
ng set apps.0.serviceWorker=false
ng build --prod
...deploy
rm dist/ngsw.json
...deploy
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'); });
});
# Install the Workbox CLI
$ npm install workbox-cli --global
# Run the wizard to generate a service worker
$ workbox wizard
module.exports = {
"globDirectory": "dist/",
"globPatterns": [
"**/*.{txt,png,ico,html,js,json,css}"
],
"swDest": "dist/sw.js"
};
module.exports = {
"globDirectory": "dist/ngPwa",
"globPatterns": [
"favicon.ico",
"index.html",
"*.css",
"*.js",
"assets/**/*"
],
"swDest": "dist/ngPwa/sw.js"
};
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, {});
platformBrowserDynamic()
.bootstrapModule(AppModule)
.then(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw-default.js')
}
});
"assets": [
{
"glob": "**",
"input": "../libraries/",
"output": "./"
}
...
]
$ npm install --save-dev workbox-build
$ workbox copyLibraries libraries/
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.`)
})
importScripts("workbox-v3.2.0/workbox-sw.js");
workbox.precaching.precacheAndRoute([])
// Features code
workbox.routing.registerRoute(
/(http[s]?:\/\/)?([^\/\s]+\/)timeline/,
workbox.strategies.networkFirst()
)
...