Template Caching in Angular – How to Avoid the Storm?

When we build a JS application, we usually start thinking about optimizing the http request, compression and, sometimes, “uglifying” the JS files.

template-caching-in-angular-how-to-avoid-the-stormBut, what about the html files? Let’s see if we can optimize the number of requests needed to load the templates for our own controllers and directives!

Using Templates Without Impacting Your Server

For a start, here are the two best ways that I know to associate a template with a directive in Angular:

1. Get the template as a string (inline):

angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    template: 'Name: {{customer.name}} Address: {{customer.address}}'
  };
});

Reference: https://docs.angularjs.org/guide/directive

2. Get the template from an external file

angular.module('docsTemplateUrlDirective', [])
.controller('Controller', ['$scope', function($scope) {
  $scope.customer = {
    name: 'Naomi',
    address: '1600 Amphitheatre'
  };
}])
.directive('myCustomer', function() {
  return {
    templateUrl: 'my-customer.html'
  };
});

Reference: https://docs.angularjs.org/guide/directive

You can also use templates when using controllers. For example, you could use the templateUrl property with a $routeProvider or use an embedded tag within an html block related to a controller.

Since the templateUrl property is usable in both contexts, let’s focus on it for the moment. Using that property means that when you need a template, the browser will grab the file from the specified URL. I.e. If you have 50 templates references, you get 50 server requests. If you have 10,000 templates references, you need to find a shelter fast to protect yourself from the incoming DevOps Storm!

As you know from all your internet reading, the good way to write templates is in separate files because it is more maintainable, extensible, (add your own positive adjective here). But the one that matters to us right now is that it needs to be easy to implement. I’m sure that if you are an angular hipster like me, you already compress, uglify and compact JS files. Basically what that means is that we are using differents systems to make the files smaller and consolidate them into one final file to minimise the number of server connections and the amount of data transferred.

Why would we not do the same with our html templates? Well, the solution probably lies somewhere between our two examples… i.e. could we have tried to stick to “template” as we were not duplicating calls but keep the idea of “templateUrl” which allows us to externalize our html work. Let’s see if we can find something to do this.

The good news is that Angular can already help you! The service that does this for us is $templateCache.

The first time a template is used, it is loaded in the template cache for quick retrieval

Yeah! we can now use a cache, nothing really special at this point but the documentation also says:

You can load templates directly into the cache in a script tag

This means that we should be able to do something like this:

<script type="text/ng-template" id="templateId.html">
  <p>This is the content of the template</p>
</script>

Exciting, this means we should be able to do a nice trick here…

Let’s consider that you have already implemented your template as a separate file i.e. you have something like this:

templateUrl: ‘my-customer.html’

What happens now if we use the $templateCache service is as follow:

  • load a script with type=”text/ng-template”
  • refer to your templates using the same ID (file path)
  • the first time the template is accessed, angular will get it from the server and add it to the $templateCache
  • every time that ID is referenced from then on, angular gets it from the $templateCache and not from the server!!

Updating Your Existing Templates to Use $cacheTemplate

Now, we’re all facing a problem. Before looking for this blog post, we’ve already implemented templates and currently it’s raining very hard on us from the DevOps cloud. This means that we need to migrate an already implement set of HTML files to the Angular.JS  caching.

The common aspect to all approaches is that you need to consolidate all those HTML files into one somewhere in your deployment cycle (using watchers in your development environment, when the page loads, …)

For example, one possible implementation in django is to create a new template tag:

from django.contrib.staticfiles import finders
from django import template

register = template.Library()

@register.simple_tag
def angular_template(relative_path):
   html = '<script type="text/ng-template" id="+ relative_path + '">'
   result = []
   for finder in finders.get_finders():
  	result.extend(finder.find(relative_path, True))
   path = result[-1]
   with open(path) as file:
  	html += file.read()
   html += '</script>'
   return html

Using the above code, you can now reference the template in the following way in your django template:

{% angular_template 'directive_path/template.html' %}

The main benefit is that you don’t need to change anything in your directive or controller to add this behavior and you can decide what content will load the “normal way.” For example, you can decide not to use this for sensitive content.

IMPORTANT: you need be sure that the templateUrl value and the id in the ng-template match (same string).

Server Side Caching

Additionally, it is usually a good idea to use a server side cache in order not to reload those templates. Let’s look at an example that uses the Django cache to make sure we only read the files once when serving the first request.

from django import template
from django.contrib.staticfiles import finders
from django.core.cache import get_cache
import hashlib

from django import template

register = template.Library()

@register.simple_tag
def angular_template(relative_path):
   cache = get_cache('angularTemplateCache')

   m = hashlib.md5()
   m.update(relative_path)
   id = m.hexdigest()

   html = cache.get(id)
   if html:
  	return html

   html = '<script type="text/ng-template" id="’ + relative_path + '">'

   result = []
   for finder in finders.get_finders():
  	result.extend(finder.find(relative_path, True))
   path = result[-1]

   with open(path) as file:
  	html += file.read()
   html += '</script>'

   cache.set(id, html)

   return html

For this code to work, you also need to set the cache in the settings file (as per https://docs.djangoproject.com/en/1.8/topics/cache/)

CACHES = {
	'default': {
    	...
	},
	'angularTemplateCache': {
    	'BACKEND': ...,
    	'LOCATION': ...,
    	'TIMEOUT': None
	}
}

Of course, as for any great trick, there are a couple of slightly negative impacts. The first one is that when you add a new directive, you now need modify your main html file.

The above implementation is currently my favorite! It feels a bit like infinite chocolate.

There are alternative ways, for example you can create a more complex tag template or build a task to generate the includes.

For completion, let’s look at a widely spread practice (that I do not particularly like). It is based on loading the $templateCache differently and creating a JS file.

var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
  $templateCache.put('templateId.html', 'This is the content of the template');
});

You can automate this process and create a JS file with this logic which you then add to your application. You can have a look at the following projects to see such implementations:

 So, we’ve looked at a couple of alternatives to  load your templates, minimize the number of connections from your browser and your network traffic, optimize the disk access from your server.

 


Do you think you’re now ready to face that Storm? Ready to start getting insights from your applications? Sign up for a free trial of Logentries today.

Posted in How To, JavaScript, Server monitoring, Tips & Tricks

Leave a Reply