Users Online
· Members Online: 0
· Total Members: 188
· Newest Member: meenachowdary055
Forum Threads
Latest Articles
Articles Hierarchy
How to use Angular 6+ Service Worker for (dynamically) loading images
How to use Angular 6+ Service Worker for (dynamically) loading images
How to use Angular 6+ Service Worker for (dynamically) loading images
Today I want to show you how to utilize the default Angular 6+ Service Worker to cache dynamically loaded assets. This is especially useful if you use a JAM Stack and you don’t know about the assets an editor may use for the content.
And since we cannot install more than one service worker for a page, it would be great if we could use the default Angular Service Worker instead of rewriting the whole service worker by ourselves.
What is the problem we want to solve?
By default, the built-in Angular Service Worker (NGSW from now on) just caches all the resources in the assets folder. In fact it’s possible to configure the NGSW to cache data groups like this (see https://angular.io/guide/service-worker-config#datagroups for more information):
export interface DataGroup {
name: string;
urls: string[];
version?: number;
cacheConfig: {
maxSize: number;
maxAge: string;
timeout?: string;
strategy?: 'freshness' | 'performance';
};
}
But one thing the documentation doesn’t tell you is that these DataGroups are only working for GET Requests which support CORS.
So how do we solve this?
I recently built a progressive web app for a client where the content comes from a Typo3 Back-End via a GraphQL API. So all the content is in a JSON format and at any position within the JSON could hide a URL to an image (or other resources).
Now we could follow two different strategies:
- Cache all images the first time they are displayed.
- Cache all images upfront when the content is loaded for the first time, e.g. after a login.
Configure the DataGroup
For both strategies we have to configure the DataGroup the same way. Lets assume your resources are stored under https://api.example.org/uploads
, then we have to add the following lines to our ngsw-config.json
:
"dataGroups": [
{
"name": "dynamicResources",
"urls": [
"**/uploads/**/*.jpg",
],
"cacheConfig": {
"maxSize": 250,
"maxAge": "7d"
}
}
]
The name
attribute is just a key for the cache storage that will hold the resources. Under urls
you find an array with all the urls this DataGroup should be active for. You don’t need to write the full url here. Please note that this is not a regex syntax, but a special glob pattern (see https://angular.io/guide/service-worker-config#urls). You can add more url patterns for additional file types.
The cacheConfig
section defines the caching policy. It’s highly recommended to specify at least the maxSize
attribute. It defines the number of requests that should be cached. For the sake of your users disk space you should set the maximum of files that are cached. Otherwise your cache can grow uncontrollably.
CORS is a must have
Now you have added your DataGroup to the ngsw-config.json
and you want to test your new setting with a standard image tag.
<img src="https://api.example.org/uploads/isthiscached.jpg" />
Full of excitement you take a look at the developer tools under Application
and you see… nothing. What’s wrong?
You have made two mistakes:
- The webserver has to support CORS for your static files directory.
- You have to tell your browser to use CORS for this image tag. For that you have to add
crossorigin="anonymous"
to your image tag.
<img src="https://api.example.org/uploads/isthiscached.jpg" crossorigin="anonymous" />
Et voilà… You have successfully implemented strategy #1. Your images are now cached automatically by the NGSW.
But why is CORS necessary?
When you make a Non-CORS request the response is opaque. The service worker does not know if the request was successful. If you write your own service worker it’d be possible to manually put an opaque response to the cache. But for security reasons the browser would add a padding to the response body and your response would be at least ~7 MB. The cache size would increase drastically.
So the NGSW doesn’t even bother to cache opaque responses.
Cache all images upfront
But that’s not enough. You want more! And I’m with you ;-)
To cache all images upfront in the moment you receive the JSON with the content, you just have to do a little more:
function onContentLoaded(contents: any) {
if ('serviceWorker' in navigator) {
const urls = new Set(...this.extractAllUrls(contents));
Array.from(urls).forEach(url => fetch(url));
}
}
You just have to extract all the urls from your servers response. Why am I using a new Set()
? Because it’s a simple way to filter out duplicates.
Then we iterate over the urls and use fetch()
. This will trigger a request and the NGSW will intercept this request automatically and cache the response. And you don’t have to worry about a reload! The second time you fetch the resources they will be served from the cache. So you don’t need to be afraid that the user has to download all images every time the page is reloaded.
Conclusion
It’s not that hard to cache images dynamically with the Angular Service Worker. But the Angular documentation misses out some important parts about when requests are cached. Especially you have to bear in mind that you need CORS enabled on your webserver.