Thinkster

Understanding Angular's $templateCache

Introduction

In AngularJS, there are multiple ways for you to specify templates in your application. We're going to go over all of them, and the differences between them, and when you should be using them in practice.

Using Templates as a String

<!DOCTYPE html>
<html>
<head>
  <title>$templateCache</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular-route.min.js"></script>
  <script src="app.js"></script>
</head>
<body ng-app="app">
  <div ng-view></div>
</body>
</html>
(function(){

  function TestCtrl() {
    this.user = {name: 'Blake'};
  }

  angular.module('app', ['ngRoute'])
  .config(function($routeProvider){
    $routeProvider.when('/', {
      controller: 'TestCtrl as test',
      template: 'Hello {{ test.user.name }}!'
    })
    .otherwise('/');
  })
  .controller('TestCtrl', TestCtrl);

})()

In our initial code, we're passing a string directly into $route for our template that we want to use with our TestCtrl. You usually wouldn't do this in your application though, as it'd be hard to maintain all of your templates in your config block. Angular allows us to specify remote templates by using the templateUrl property instead of template. Let's try this out in the next example.

Using a Remote Template

Hello {{ test.user.name }}!
function TestCtrl() {
  this.user = {name: 'Blake'};
}

angular.module('app', ['ngRoute'])
.config(function($routeProvider){
  $routeProvider.when('/', {
    controller: 'TestCtrl as test',
    templateUrl: 'test.html'
  })
  .otherwise('/');
})
.controller('TestCtrl', TestCtrl);

{info} For this example to work, you need to be serving your files off of a server and not opening your files directly through your filesystem (i.e. a URL starting with file://). We recommend using browser-sync (can be installed via npm install browser-sync -g) or python (by running python -m SimpleHTTPServer) to do this.

If we look at our requests in the console when the page loads, we can see that a separate request is used to retrieve our template from test.html This is Angular requesting the file and adding the template to $templateCache. We can demonstrate this behavior by injecting $templateCache into TestCtrl and then logging $templateCache.get('test.html') to the console. When we refresh, we'll see that Hello {{ test.user.name }}! has been logged out to the console.

function TestCtrl($templateCache) {
  this.user = {name: 'Blake'};

  console.log($templateCache.get('test.html'));
}

Using remote templates makes the most sense for your development environment. It allows you to logically split your templates out into separate files. However, as your application grows, you may run into performance issues when you have to download multiple templates on each page load, which can make you application appear slow since your templates need to be downloaded before they can be rendered. In development this isn't an problem since you're working off of localhost, so latency/network is usually not an issue. In the next examples, we'll show you a couple ways you prepopulate $templateCache before Angular loads.

Using Inline Templates:

Angular ships with a script directive, which when passed text/ng-template as the text attribute, Angular will recognize that it is a template meant to be used with our application and put any content between the script tags into $templateCache, keyed by the id attribute.

<!DOCTYPE html>
<html>
<head>
  <title>$templateCache</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular-route.min.js"></script>
</head>
<body ng-app="app">
  <div ng-view></div>
  <script type="text/ng-template" id="test.html">
    Hello {{ test.user.name }}!
  </script>
</body>
</html>

Take note that the template must be within our ng-app directive in order for Angular to recognize it. Now if we look at our console, we see that there isn't an additional network request isn't made to retrieve our template, and TestCtrl is still logging out our test.html template. This is convinient as we don't need to change our javascript at all, Angular automatically looks at $templateCache before trying to retrieve a remote template. This method isn't ideal for development though, as it means all of your templates need to be inlined within index.html which can make it really hard to manage all your templates in one file, so it's recommended to stick with remote templates while you're developing. In the next example, we'll show you the recommended way of including templates in your production application.

Populating $templateCache Directly

$templateCache is simply a key-value store where you can get and put templates. Angular will automatically check $templateCache whenever you use templateUrl in your application or when you specify a template src with ng-include before attempting to retrieve your template from a remote location.

<!DOCTYPE html>
<html>
<head>
  <title>$templateCache</title>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.5/angular-route.min.js"></script>
  <script src="app.js"></script>
</head>
<body ng-app="app">
  <div ng-view></div>
</body>
</html>

Since $templateCache is a service, we won't be able to access it in the config phase of our app. The earliest we can access $templateCache is in the run phase for our app. Angular modules can have as many run blocks as needed.

function TestCtrl($templateCache) {
  this.user = {name: 'Blake'};

  console.log($templateCache.get('test.html'));
}

angular.module('app', ['ngRoute'])
.config(function($routeProvider){
  $routeProvider.when('/', {
    controller: 'TestCtrl as test',
    templateUrl: 'test.html'
  })
  .otherwise('/');
})
.controller('TestCtrl', TestCtrl);

angular.module('app').run(function ($templateCache){

});

Now we'll be able to put templates directly into $templateCache. Let's put our test.html template into $templateCache via this run block.

angular.module('app').run(function ($templateCache){
  $templateCache.put('test.html', 'Hello {{ test.user.name }}!');
});

Now when we refresh the page, we'll see that no additional call is required to get our test.html template. This is the preferred way of loading templates in production environments. You'd never want to write that run function manually, since it'd be hard to maintain all your templates as strings and in one file. You can generate this function by creating a task that iterates over all your templates and stringifies them into $templateCache as part of your build process. You should stick with the remote template method in development unless you're using an asset processor in development (such as a gulp or grunt tasks that watches and processes your assets, or sprockets which ships with rails).

Here are a few modules that can help you inject templates into $templateCache: - grunt-angular-templates for use with Grunt - gulp-angular-templatecache for use with Gulp - angular-rails-templates for use with Rails/Sprockets

If you're using the remote template method in development, the only difference in your production environment should be the addtional run function used for populating $templateCache. Either way, you shouldn't need to change the way you specify templates to templateUrl since Angular will automatically fall back to the remote template method if it can't find the template you've specified in $templateCache.