Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jamie Krug and Simon Free
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Jamie Krug@jamiekrug ) and Simon Free@simonfree )

Using ngRepeat With ngInclude Hurts Performance In AngularJS

By Ben Nadel on

Yesterday, I took a quick look at the performance impact of using directive templates in AngularJS. The impact was small and definitely worthwhile considering the benefits of creating reusable code. Today, however, I wanted to look at another approach to reusing code that does have a significant performance impact: using ngRepeat with ngInclude in AngularJS.


 
 
 

 
 
 
 
 

Run this demo in my JavaScript Demos project on GitHub.

For this experiment, as with yesterday's, we're going to render two different lists that use the same content. In order to keep the code DRY (Do not Repeat Yourself), we're going to define the ngRepeat content as template that gets included using the ngInclude directive. I'm not going to bother showing the "control" experiment - the code from yesterday (you can watch the video); so, let's jump right into the ngRepeat-ngInclude demo:

  • <!doctype html>
  • <html ng-app="Demo">
  • <head>
  • <meta charset="utf-8" />
  •  
  • <title>
  • Using ngRepeat With ngInclude Hurts Performance In AngularJS
  • </title>
  •  
  • <link rel="stylesheet" type="text/css" href="./demo.css"></link>
  • </head>
  • <body ng-controller="AppController">
  •  
  • <h1>
  • Using ngRepeat With ngInclude Hurts Performance In AngularJS
  • </h1>
  •  
  • <h2>
  • Using ngRepeat With ngInclude
  • </h2>
  •  
  • <p>
  • <a ng-click="toggleLists()">Toggle Lists</a>
  • </p>
  •  
  • <div
  • ng-if="isShowingLists"
  • ng-include=" 'list.htm' ">
  •  
  • <!-- Content pulled-in as template to simulate real-world architecture. -->
  •  
  • </div>
  •  
  •  
  • <!--
  • I am the template used to render the main page.
  • --
  • NOTE: Both ngRepeat directives identify their item as "person" because the
  • ngInclude'd template does not have the ability to differentiate between different
  • contexts (like an attribute-based binding might).
  • -->
  • <script id="list.htm" type="text/ng-template">
  •  
  • <div class="friends">
  •  
  • <h2>
  • Friends
  • </h2>
  •  
  • <ul>
  • <li
  • ng-repeat="person in friends track by person.id"
  • ng-include=" 'person.htm' ">
  •  
  • <!-- Content provided by ngInclude. -->
  •  
  • </li>
  • </ul>
  •  
  • </div>
  •  
  • <div class="enemies">
  •  
  • <h2>
  • Enemies
  • </h2>
  •  
  • <ul>
  • <li
  • ng-repeat="person in enemies track by person.id"
  • ng-include=" 'person.htm' ">
  •  
  • <!-- Content provided by ngInclude. -->
  •  
  • </li>
  • </ul>
  •  
  • </div>
  •  
  • </script>
  •  
  •  
  • <!-- I am the template included by ngInclude. -->
  • <script id="person.htm" type="text/ng-template">
  •  
  • {{ person.id }} &mdash; {{ person.name }}
  •  
  • </script>
  •  
  •  
  • <!-- Load scripts. -->
  • <script type="text/javascript" src="../../vendor/angularjs/angular-1.3.6.min.js"></script>
  • <script type="text/javascript">
  •  
  • // Create an application module for our demo.
  • var app = angular.module( "Demo", [] );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // I control the root of the application.
  • app.controller(
  • "AppController",
  • function( $scope ) {
  •  
  • // I hold the lists being rendered.
  • $scope.friends = buildList( "Sarah", 1000 );
  • $scope.enemies = buildList( "Shane", 1000 );
  •  
  • // I determine if the lists should be shown.
  • $scope.isShowingLists = false;
  •  
  •  
  • // ---
  • // PUBLIC METHODS.
  • // ---
  •  
  •  
  • // I toggle the rendering of the lists (physically removing them from
  • // the page if they should not be there).
  • $scope.toggleLists = function() {
  •  
  • $scope.isShowingLists = ! $scope.isShowingLists;
  •  
  • };
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I build a list of people using the given size.
  • function buildList( name, count ) {
  •  
  • var people = [];
  •  
  • for ( var i = 1 ; i <= count ; i++ ) {
  •  
  • people.push({
  • id: i,
  • name: name
  • });
  •  
  • }
  •  
  • return( people );
  •  
  • }
  •  
  • }
  • );
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, each ngRepeat template also uses the ngInclude directive to reference a common template. Notice that the ngRepeat iteration item is "person" in both cases. We have to do this because the included template has no way to differentiate the calling context (ie, friend vs. enemy).

This page works just as you would expect; however, it takes over a second to render (where as the control takes about 100ms). And, when we look at the Chrome dev-tools timeline, we can see why:


 
 
 

 
 Using ngRepeat with ngInclude in AngularJS has a significant performance impact. 
 
 
 

The browser spends over a second just parsing HTML. The reason for this is that the ngInclude directive is recompiling the content of the included template in every single linking phase of the ngRepeat. This means that if there are 100 ngRepeat clones, the ngInclude template gets compiled 100 times. This is a striking difference when compared to the use of a directive template which only compiles the template once.

NOTE: After digging through the AngularJS source code, I believe the directive only compiles the template once by queuing up the linking functions. Then, once the template is available, AngularJS clones and links the previously-compiled node using the queued transclusion functions. Or, as best I can tell - this portion of the AngularJS code is particularly cryptic.

I think the lesson learned here is that if you are using ngInclude to reuse code (such as inside an ngRepeat), it's probably better off inside a "component" directive. This will create a more responsive experience for your users. Of course, if you're using ngInclude to render sections of a page in a non-reusable manner, using ngInclude should be totally fine - ngInclude is awesome, I'm not trying to give it a bad name.




Reader Comments

@Phil,

They are pretty cool. I'm still learning about how to best incorporate them; and how to think about what goes in the directive vs. what goes in the controller. But, definitely growing to like them more.

Reply to this Comment

I've been looking at React.js recently and the viritual DOM really helps for actions like this. I may have heard Angular 2 is addressing this too. More than a few performance tests comparing the two out there.

Reply to this Comment

@Brett,

From what I have heard, ReactJS can be faster than AngularJS when Angular does a lot of DOM creation and destruction. However, if you can get AngularJS to limit the amount of DOM manipulation it does, I've heard that the two libraries are mostly on-par with performance.

This is why I thought the "track by" feature of ngRepeat was the most exciting update of AngularJS 1.2:

http://www.bennadel.com/blog/2556-using-track-by-with-ngrepeat-in-angularjs-1-2.htm

... it allows the ngRepeat data to be refreshes *without* creating new elements (unless of course there are new items in the collection).

That said, this is all from things I've heard in passing. I'm curious to check out the ReactJS framework in general.

Reply to this Comment

@Pete,

Very cool. I tried to look through the part of the AngularJS source code that deals with how directives handle the templateUrl. I don't know how you guys follow some of that code - there is *so much going on*! I guess once you know build the mental model and know where to look, it gets easier.

I keep going back to how the ngInclude and the directive templateUrl differ; because, at first glance, it feels like they are doing the same thing - including a template and replacing content.

But, once you start to noodle on it a bit more, you realize that the ngInclude SRC can be dynamic, where as the directive template cannot. Meaning, we always know exactly what the directive template is going to be, even in the context of an ngRepeat; but, the ngInclude src can actually be different for every ngRepeat clone (if the path is derived from the $index, for example). As such, I assume you can't make the same assumptions with an ngInclude that you can with a directive.

Reply to this Comment

@All,

After I posted about this, Igor Minar challenged me with the idea that all ngInclude directives could be replaced with "component directives." I attempted my first exploration in this vein:

http://www.bennadel.com/blog/2740-replacing-nginclude-with-component-directives-in-angularjs.htm

It's actually pretty cool because it combines all three ingredients - Controller, View, and Link function. And, since it's a directive tempatelUrl, it has added optimizations, related back to what I was just discussing with Pete (above).

Reply to this Comment

If It works wrong the cause is a wrong API program (Google) because the problem only could be occurred with the get of the template but it is cached. Of course this is not a problem. If the problem is the $compile the process should be the same that you wouldn't have the ng-include. I don't know where can be the neck bottle.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.