Setting up service worker to an existing Angular project

I wanted Makro to work at least partially even if your internet connection goes down. For now, I didn’t make it a fully fledged progressive web app (later PWA) but that doesn’t mean that I am not going to make it one. I’ll probably keep working on it every now and then. But at the time being, I am more than happy with the result. Let us get started then.

So, you got your wonderful application made with modern Angular and you want to add service worker to it. Or not. You might ask what the hell is a service worker and why should you even care? I’m glad you asked.

A service worker is a script that is separate from your web site that the browser runs in the background. And these days they include features such as background sync and push notifications. One thing to note though is that service worker requires you to have HTTPS setup on your site. If you want to learn more, click here.

Now we have an idea about what it is. I believe the next question was that you should you care? For me, it was the fact that it can make your web site to work even if the internet connection of the user goes down while browsing your site or isn’t even there to begin with (given that the initial caching has been done before that) but your mileage may vary.

useless meme
Having an active connection is overrated.

Time to get our hands dirty. First, we want to add the official Angular PWA package to your project. Go to your Angular project folder and type:

ng add @angular/pwa

That will add Angular service worker package, enable the build support for it in the CLI, update your app.module to include the service worker, update your index.html file, installs icons for PWA and last but not least it will create a service worker configuration file that is called ngsw-config.json.

One drawback is that if you want to test your service worker you can’t use ng serve because it does not work with service worker. Instead you have to build your project and serve it. You can build it by typing:

ng build --prod

When the build process is done, you can serve it with http server of your choosing. I used http-server. You can, too, by installing it (if you don’t have it already) by typing:

npm i -g http-server

After that you can serve your Angular app with it. Just go into the folder where it was build. In my case it was dist/frontend because my project was called frontend. Then type:

http-server -p 4200 (or whatever port you want to use)

Now that we have a service worker registered and working we want to do some configuration to it to better suit our needs. Configuring the service worked is done via ngsw-config.json -file. By default, it has a root page defined (index.html) and then these so-called asset groups. The asset groups are static files that are to be cached so that they are available even if the internet connection goes down. For me, it looks something like this.

  "assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"]
}
}
]

What it does is that the service worker will fetch those asset files as soon as the page loads since the install mode is prefetch. The alternative to that is setting it to lazy which means that the files won’t be fetched until they are needed for the first time. The upside of this is that it doesn’t take all the users bandwidth at the beginning but the downside is of course that if some file is not needed before the connection goes down, it can’t be used since it wasn’t cached.

There is also an update mode which has the same settings as install mode. The update mode is used to decide how the static files should be updated. By setting it to prefetch and you happen to update your Angular app while somebody is already browsing it, the service worker will know that there’s a new version available of your static files and fetch them. If it’s set to lazy, it won’t fetch them until needed.

If you, like me, use an external font and want that to be cached too, you can add another key to your resources there and that is urls like this:

  "assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"updateMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"]
},
"urls": ["https://fonts.googleapis.com/css?family=Roboto", "https://fonts.gstatic.com/**"]
}
]

By now, we have a somewhat working app even when offline but you, like me, might want to fetch some data from an API and cache that, too. Luckily, setting that up is quite straight forward as well. Static files were all defined in asset groups and stuff that is fetched from an API should be put into data groups array.

"dataGroups": [
{
"name": "posts",
"urls": ["https://jsonplaceholder.typicode.com/posts"],
"cacheConfig": {
"maxSize": 1,
"maxAge": "12h",
"timeOut": "5s",
"strategy": "freshness"
}
}
]

What’s going on there? First of all, you define a name which can be whatever you like since It doesn’t affect the functionality. Then you define the URLs that you want this group to handle. In my case, I added only one. Next up is the cache config. The first option there is max size which is used to define how many entries from that group is going to be saved in the cache. In my case, I put 1 since it doesn’t matter for me because it will always return just one entry. Do not that it does not mean the number of posts but the number of responses from that URL. But if you put the URL like yourapi.com/* which would return like 8 different responses you’d set the max size to be 8.

Then we define the max age which the service worker uses to know when should the cache be definitely invalidated. You can define it by using u for milliseconds, s for seconds, m for minutes, h for hours, and d for days. So it could be something like “1d12h30m15s10u”.

After max age we have timeout. Now that is being used for how long will the service worker wait for a response from an API before using the cache.

Last but not least we have the strategy. I have put there freshness which means that it will always first try to fetch new data and after timeout return the cached data if we have any. The alternative to that is performance which will try to get something on the screen as fast as possible and use max age to determine if it should definitely fetch new data from the API.

As you probably already noticed the freshness strategy takes the timeout setting into account when deciding if to use cache or not and the performance strategy will use max age for that. Now your ngsw-config.json might look something like this:

{
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": ["/favicon.ico", "/index.html", "/*.css", "/*.js"]
},
"urls": ["https://fonts.googleapis.com/css?family=Roboto", "https://fonts.gstatic.com/**"]
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": ["/assets/**"]
}
}
],
"dataGroups": [
{
"name": "posts",
"urls": ["https://jsonplaceholder.typicode.com/posts"],
"cacheConfig": {
"maxSize": 1,
"maxAge": "12h",
"timeOut": "5s",
"strategy": "freshness"
}
}
]
}
Boom! Your service worker is now done.

After setting all that up we got an application that would work offline as well and it was rather an easy process to achieve that. Yay us.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *