PWA, progressive web apps, gives our browser native superpowers, such as offline availability and being able to install websites as a native app. The key to creating maintainable and performant offline apps is to handle caching without cluttering your code with complicated caching logic. Caching is a cross-cutting concern in the whole app which should be handled with Aspect-oriented programming. Aspect-oriented programming is when you allow the separation of cross-cutting concerns, such as logging, state synchronization and caching, by setting up middleware to hook these into certain events in the system, keeping the cross-cutting concerns easily maintainable by handling them in a configuration instead of in the application code. ServiceWorker is a way to handle, among others, caching in a web app, by hooking into XHR requests and allowing you to configure the caching, keeping your application code free of caching logic.
This post will show you how easy you can make an Angular app offline available as well as boosting the performance, by setting up ServiceWorker caching using Angular PWA.
Setting up request caching in an Angular app
We are going to build upon my TODO app and make it more performant by enabling request caching as well as static content caching. I have already created a post about how to set up a basic offline app using Angular PWA you can read here. For clarity, I’m going to take you from the beginning to the end, to show you how to get around the common obstacles you most likely are going to face when doing request caching.
For setting up request caching in an Angular app we are going through these steps:
ng add @angular/pwa
npm run buildto build the project
http-serverto serve the PWA
http-server -p 8080 (static content is cached, but not API requests, yet)
- Cache API requests with performance (changes rarely)
- Cache API requests with freshness (changes often)
Seems simple, huh? Let’s do this.
Prefer video? Watch me do this:
Steps 1-4: Setting an Angular app up for static content caching
For details about these steps, check my post on setting up an Angular PWA.
Some common problems when doing these steps:
- AppModule can’t be located when using path alias in tsconfig. Therefore, import AppModule using a relative path in main.ts
- Make sure you have the latest version of Angular CLI using
npm i @angular/cli -gbefore doing these steps
- Remove the dist folder with
npm i -g rimraf) before rebuilding because http-server can lock files
- Environment files can’t contain constructors or methods, because Angular PWA is using environment in a decorator when checking if prod
Some custom configuration for this post
To demonstrate what and how to cache I have added a new endpoint to the express server, from my TODO app. The express server should now have a fresh-todo-list endpoint like this:
Now there are two endpoints for getting TODO lists, one for returning a cached TODO list and one for always trying to get a fresh TODO list first. These are for demonstrating two caching strategies we are going to work with later. The Angular app uses these in the
Steps 5-6: Setting up request caching
Now when we have the basic app up and running, we need to cache the HTTP requests to make it fully offline
Angular PWA makes this easy for us because we only need to configure the
ngsw-config.json. Before we start to setup caching we need to determine what to cache and how to cache it.
What should you cache?
How should you cache API requests?
When determining how you should cache your API requests, you should group the API endpoints in; values that change often and values that change rarely to determining the right caching strategy. In our TODO app, the translations are changing rarely, so these can benefit from being cached for a longer time. The actual TODO list might change more often and should optimize for freshness instead of performance to ensure the user sees the newest TODO items.
Angular allows us to configure how endpoints should be cached by supporting two caching strategies: Performance and freshness.
Cache strategy: performance
Cache strategy performance will go to the cache until the cache has expired according to the maxAge property. This will cache the URLs specified as glob expressions in the urls property as up to the number of cached requests specified in maxSize.
Cache strategy: freshness
Cache strategy freshness will first request the API and will only resolve to the cache after a timeout, specified in the timeout property, is reached. This strategy allows us to specify a maximum tolerable delay from the API. Likewise, this strategy will store the number of responses specified in maxSize and will keep it until maxAge is reached.
If the app is offline, both of these cache strategies will get data from the cache immediately.
The complete configuration looks like this:
For demonstration purpose, I have configured an offline available app that will cache the TODOs but allows for manually updating TODO items by calling the new TODOs endpoint which uses the freshness caching strategy. The translations and all API endpoints, as default, are considered as rarely changing (not very realistic, but demonstrates the different caching strategies) in this demo and are therefore using the performance strategy. In a more realistic scenario, you would typically have most endpoints using the freshness strategy and a few that changes so rarely that the performance strategy is appropriate.
We should now be able to run the whole app by building with the command:
ng build --prod
Then going to the build folder and serve it using http-server:
http-server -p 4200
Now when navigating to http://127.0.0.1:4200, you should be able to see an app that will work offline while allowing for getting new TODO items by calling the update TODO list button:
This demonstrates how the ServiceWorker is caching the XHR requests on the first run. After this, we are able to turn the app offline and it is still working!
The complete code repo can be found on my Github.
Caching up (pun intended)
In this post, we extended my existing Angular PWA with caching of HTTP requests, allowing for a fully offline available PWA. We looked into how to enable ServiceWorker caching in an Angular app, focusing on caching the HTTP request from an API. We discussed what to cache and how to cache it using the two caching strategies: Performance and freshness, where the first will go to the cache until it expires and then fetch from the API. In contrast, the freshness caching strategy will optimize for fresh data by first trying to get the data from the API, until a specified timeout is reached. In the case of a request timeout, it will resolve to the cache. Even though we used caching here to make our app offline available, this also speeded up the load times on the app considerably by having all the static content and data ready in the cache.
I hope you liked this post. Remember to comment, subscribe to the blog and follow me on Twitter. Will cache up on you next time!