How to get rid of template-caching issues in Angular

Madeleine Redl
willhaben Tech Blog
4 min readMay 24, 2018

--

Photo by Eugenio Mazzone on Unsplash

Recently, we received a user complaint about a bug we could not reproduce. It turned out that it was a caching issue, and the user had to clear his browser cache to resolve the problem. Since this was not the first time this had happened, I was encouraged to find a solution so that these annoying caching problems would never occur again.

First, I want to explain how the caching problem happened. In our application, we use AngularJS and had made a small code change. We renamed the scope variable from jobPositions to selectableJobPositions in our TypeScript file:

this.$scope.selectableJobPositions = …

We also corrected the variable in the corresponding template:

<select data-ng-options=”jp for jp in selectableJobPositions”></select>

In our build process, all TypeScript files were compiled and bundled into a common application JavaScript file and a version number was applied. This ensured that the browser fetched the JavaScript file from the server whenever an update was made to the TypeScript file. However, since the max-age in the Cache-Control header of the html files was set to seven days, it was likely that an html file was loaded from the browser’s cache.

In the example above, this meant that the user saw an empty dropdown, because the html had not updated and the select element still referenced the jobPositions variable, which no longer existed in JavaScript.

Incompatibility due to cached html

At first, one might think that the best solution is to adapt some response headers for the html files. Providing a validation token in the ETag header and setting “Cache-Control: no-cache” for all html responses would clearly solve the problem, but this would also mean that the browser has to validate that the cached html file was not updated with every page load. Since that would have a negative impact on the page load performance, I wanted to find a better solution.

Using gulp-angular-templatecache

In AngularJS, it is possible to use $templateCache, which is a simple key-value store for templates. The best way to use $templateCache is to integrate it into your build process, for example by using the gulp plugin gulp-angular-templatecache. The following gulp task creates a module where all template files were registered in the $templateCache. This module is saved to the “templates.js” file in the defined destination directory.

const templateCache = require('gulp-angular-templatecache');gulp.task("templates:build", function() {    return gulp.src("/**/*.html")
.pipe(templateCache("templates.js", {standalone: true}))
.pipe(gulp.dest(destDir));
});

The generated “templates.js” will look something like this:

angular.module(templates', []).run(['$templateCache',   function($templateCache) {
$templateCache.put('app/navigation.html', '… content …');
$templateCache.put('app/job.html', '… content …');
$templateCache.put('lib/tooltip.html', '… content …');

}
]);

In the main app module, we have to add the dependency to the generated templates module.

angular.module('app', ['templates']);

Every time a template is requested via templateUrl or ng-include, Angular checks to see if it exists in the $templateCache and loads it from there. Consequently, the browser no longer needs to request html files, which reduces the number of requests for the page load.

Since the gulp-angular-templatecache plugin does not process the html content in any way, it might be a good idea to minimize it before registering it to the $templateCache. This can be achieved with another plugin, gulp-htmlmin.

Using webpack angular2-template-loader

Webpack is a widely used build tool that preprocesses and bundles various application resources like .ts, .html, and .css files. For applications using Angular 2 or higher, there is a special loader, called angular2-template-loader, that inlines all template code into the corresponding angular components. In your webpack.config.js, you add the angular2-template-loader to your current typescript loader. Furthermore, you need to define a loader that can process html files. This could be, for example, raw-loader or html-loader. Your Webpack loader configuration might look something like this:

module: { 
rules: [
{
test: /\.ts$/,
loaders: [
'awesome-typescript-loader',
'angular2-template-loader'
]
},
{ test: /\.html$/, loader: 'html-loader' }
]
}

You don’t have to adapt the way you reference the templates. Instead, angular2-template-loader adds the corresponding require statement to all paths, which can then be handled by the html-loader. However, when you are using Webpack with AngularJS and can therefore only use the html-loader, you will have to manually add the require statements.

Final thoughts

The solution you choose will certainly depend on how easily it can be integrated into your current build process. For our application, we decided on the gulp-angular-templatecache solution, because we already use gulp for our front-end build. This made it the easiest and quickest solution to implement.

Note that there are more possible solutions for avoiding template-caching issues than those presented in this article. It is important to ensure that your implemented solution has no negative impact on the performance or other metrics of your web application.

--

--