Maxim Salnikov
Angular GDE
How to build an offline-ready Angular app
Developer Engagement Lead at Microsoft
App
Service worker
Cache
fetch
self.addEventListener('fetch', event => {
// Serve assets from cache or network
})
Browser
self.addEventListener('install', event => {
// Use Cache API to cache html/js/css
})
self.addEventListener('activate', event => {
// Manage versions
})
Angular
Service Worker
# Installing the Workbox Node module
$ npm install workbox-build --save-dev
On every app build
import {
precacheAndRoute,
createHandlerBoundToURL
} from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
// Precaches and routes resources from __WB_MANIFEST array
precacheAndRoute(self.__WB_MANIFEST);
// Setting up navigation for SPA
const navHandler = createHandlerBoundToURL("/index.html");
const navigationRoute = new NavigationRoute(navHandler);
registerRoute(navigationRoute);
const { injectManifest } = require("workbox-build");
let workboxConfig = {
swSrc: "src/service-worker.js",
swDest: "dist/prog-web-news/sw.js",
// + more on the next slide
};
injectManifest(workboxConfig).then(() => {
console.log(`Generated ${workboxConfig.swDest}`);
});
globDirectory: "dist/prog-web-news",
globPatterns: ["index.html", "*.css", "*.js", "assets/**/*"],
globIgnores: [
"**/*-es5.*.js", // Skip ES5 bundles for Angular
],
// Angular takes care of cache busting for JS and CSS (in prod mode)
dontCacheBustURLsMatching: new RegExp(".+.[a-f0-9]{20}.(?:js|css)"),
// By default, Workbox will not cache files larger than 2Mb
// (might be an issue for dev builds)
maximumFileSizeToCacheInBytes: 4 * 1024 * 1024, // 4Mb
import { precacheAndRoute } from "workbox-precaching";
import { NavigationRoute, registerRoute } from "workbox-routing";
...
precacheAndRoute([
{ revision: "866bcc582589b8920dbc5bccb73933b1", url: "index.html" },
{ revision: null, url: "styles.c2761edff7776e1e48a3.css" },
{ revision: null, url: "main.3469613435532733abd9.js" },
{ revision: null, url: "polyfills.25b2e0ae5a439ecc1193.js" },
{ revision: null, url: "runtime.359d5ee4682f20e936e9.js" },
{
revision: "33c3a22c05e810d2bb622d7edb27908a",
url: "assets/img/pwa-logo.png",
},
]);
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'dist/prog-web-news/sw.js',
output: {
file: 'dist/prog-web-news/sw.js',
format: 'iife'
},
plugins: [ /* Next slide */ ]
}
plugins: [
resolve(),
replace({
'process.env.NODE_ENV': JSON.stringify('production')
}),
terser()
]
"build-pwa":
"ng build --prod &&
node workbox-inject.js &&
npx rollup -c"
import { Workbox, messageSW } from 'workbox-window';
...
ngOnInit(): void {
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
// Reload-to-Update flow Using messageSW
// See demo repo aka.ms/angular-workbox
}
}
import { registerRoute } from "workbox-routing";
import {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
} from "workbox-strategies";
// Gravatars can live in cache
registerRoute(
new RegExp("https://www.gravatar.com/avatar/.*"),
new CacheFirst()
);
// Keeping lists always fresh
registerRoute(
new RegExp("https://progwebnews-app.azurewebsites.net.*posts.*"),
new NetworkFirst()
);
// Load details immediately and check and inform about update right after
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';
registerRoute(
new RegExp("https://progwebnews-app.azurewebsites.net.*posts/slug.*"),
new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin(),
],
})
);
import { clientsClaim } from "workbox-core";
// Claiming control to start runtime caching asap
clientsClaim();
Application's first load timeline
import {
googleFontsCache,
imageCache
} from "workbox-recipes";
// GOOGLE FONTS
googleFontsCache({ cachePrefix: "wb6-gfonts" });
// CONTENT
imageCache({ maxEntries: 10 });
import { offlineFallback } from 'workbox-recipes'
import { precacheAndRoute } from 'workbox-precaching'
// Include offline.html, offline.png in the WB manifest
precacheAndRoute(self.__WB_MANIFEST)
// Serves a precached web page, or image
// if there's neither connection nor cache hit
offlineFallback({
pageFallback: "offline.html",
imageFallback: "offline.png"
});
// Adding you own event handlers
self.addEventListener("periodicsync", function (event) {
// Your code
});
// Using existing Workbox plugins
import { BackgroundSyncPlugin } from 'workbox-background-sync';
const bgSyncPlugin = new BackgroundSyncPlugin('myQueue', {
maxRetentionTime: 24 * 60 // Retry for max of 24 Hours
});
// Writing your own Workbox plugins
import { MyBackgroundFetchPlugin } from 'my-background-fetch';
Maxim Salnikov
@webmaxru
Maxim Salnikov
@webmaxru