DEV Community

loading...
Cover image for Setting up service worker to an existing Angular project

Setting up service worker to an existing Angular project

maxaboxi profile image Arto Tukki ・5 min read

A slightly modified version of this posted to my blog previously.

So, you got your wonderful application made with modern Angular and you want to add a 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 about service workers, 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.

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 built. 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"
      }
    }
  ]
}

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.

Discussion (4)

pic
Editor guide
Collapse
chaoyang profile image
Chao Yang

so one question, since freshness only takes timeout into account, why is the maxAge required?

And in your example, the maxAge is 12h, which is definitely not fresh.

Collapse
tonyuifalean profile image
Dragos A. Uifalean

What about the non CLI projects? Is there a way to add the Service Worker?

Collapse
dr5hn profile image
Darshan Gada 👨‍💻

@maxaboxi I've tried adding a wildcard urls to dataGroupss..
https://api.example.com/api/v1/** But it doesn't really Works !!

Any thoughts ?

Collapse
dalud profile image
Jaakko Niska

Any idea on the default and maximum values for "maxAge" in dataGroups: cacheConfig?
I've set mine to "9999d" but the data didn't persist after five days of downtime.