Find out how to make cache busting static files (images, pdfs, pngs, etc.) for the Angular application.
Images and other assets you have in your assets folder that you reference in Angular templates are not hashed, and we are going to solve that problem.
What is cache busting in Angular?
When building, Angular will add a hash to JavaScript, CSS, and assets referenced in the CSS files, but not to any other assets like pdfs, images, documents, and the like.
Cache busting is a method that lets the browser load the most recent version of a file instead of one that has already been cached. A static file that has been cached can be kept for a very long time until it eventually expires. When you update a website with the same file name, then the browser will provide not your fresh file but what’s available in the browser cache, so the user won’t be able to notice the modifications.
Cache busting solves the browser caching issue by using a unique file version identifier or by adding a unique URL parameter to the asset URL to tell the browser that a new version of the file is available. Therefore, the browser doesn’t retrieve the old file from the cache but rather makes an HTTP request to the origin server for the new file.
The solution
The solution would be to search for static assets, create a list of them with their hashes, and save the list as a JSON file.
This is useful when you want to change the same static asset without changing the name and ensure that the browser will always fetch the latest version.
How to
In general, there might be very different solutions to that problem, but we are going to solve it in the following way:
- Install npm package @sitelintcode/angular-static-assets-hash.
- We’ll use the CLI command
npx createAngularStaticHashes
. - By default, the package searches for static assets in the folder
[angular root]/assets/images
, but you can set any location through the parameter--staticAssetsPath=[path to static assets]
. - The package gathers a list of all files from a given location, but you can change that by adding the parameter –
-globPattern=[glob pattern]
. Default:/**/*
The final result is the list of all assets collected in a single file called assets.json
with a path and the file hash.
assets.json
{
"assets/images/example.png": "iY5RY0G8wePLPRkZSTgW2XYZFZ7kQOqXoJTFpQFG5nI",
"assets/images/avatar.svg": "7A7qFs_iOghsdUtLG-7y-5cxT3FC8S_BRXl5ldsNY7Y",
"assets/images/body-background.svg": "K2FTBtDsxgKLQFr4BUW1ptnLWqPCKPyGypHCBTfcctQ",
"assets/images/icons.svg": "Ka-ngr7Fht6ucmN03kzJeMN7f2iOtnkD-D63QJ01jhM"
}
Angular
Once the list of static assets is created, we need to have a way to use it with Angular. For that purpose, we can use an Angular Pipe.
Here is an example of the Pipe code:
import { Pipe, PipeTransform } from '@angular/core';
import staticAssetsList from '../../assets.json';
@Pipe({
name: 'fileHash'
})
export class FileHashPipe implements PipeTransform {
private staticAssets: { [key: string]: string };
constructor() {
this.staticAssets = staticAssetsList;
}
private addParamsToUrl(givenUrl: string, urlParameters: string): string {
if (typeof urlParameters !== 'string' || urlParameters.length === 0) {
return givenUrl;
}
const urlSplitByHash: string[] = givenUrl.split('#');
const hash: string = urlSplitByHash[1] || '';
const params: string[] = urlParameters.split('&');
let url: string = urlSplitByHash[0];
if (url.indexOf('?') === -1) {
url += '?';
} else {
url += '&';
}
url += params.map((paramItem: string): string => {
const p: string[] = paramItem.split('=');
return `${p[0]}=${window.encodeURIComponent(p[1])}`;
})
.join('&');
url = url.slice(0, -1); // remove last &
return hash ? `${url}#${hash}` : url;
}
private getHashForStaticAsset(assetPath: string): string {
const path: string = assetPath.split('#')[0];
if (typeof ResourceUtility.staticAssets[path] === 'string') {
return this.addParamsToUrl(assetPath, `c=${this.staticAssets[path]}`);
}
return '';
}
public transform(filePath: string): string {
const filePathWithCacheHash: string = this.getHashForStaticAsset(filePath);
return filePathWithCacheHash;
}
}
In Angular code, you can use it the following way:
<img attr.src="{{ 'assets/images/example.png' | fileHash }}" alt="">
The result of the above code will give:
<img src="assets/images/icons.svg?c=Ka-ngr7Fht6ucmN03kzJeMN7f2iOtnkD-D63QJ01jh" alt=""></use>
The hash is quite useful when we want to manage, e.g., one file with all <svg>
icons. While you could change your single file with all <svg>
icons all the time, your code remains the same.
<svg aria-hidden="true" focusable="false" viewBox="0 0 48 48" width="32" height="32">
<use attr.href="{{ 'assets/images/icons.svg#image-logo' | fileHash }}"></use>
</svg>
Feedback
The approach is one possible way to handle static assets and their cache busting. Feedback is very welcome.
Founded issue? Report it directly on Github @sitelintcode/angular-static-assets-hash repository.
Workable example
You can see the implementation by looking at the source code of our SiteLint Platform, managed by the Angular framework.
Comments