Directive Templates Have A Small Impact On Performance In AngularJS
All developers want to keep their code DRY (Do not Repeat Yourself). But, as with all things computer science, DRYness can be a tradeoff, perhaps for performance. In AngularJS, directive templates allow code to be reused in multiple places; but, what impact does this have on rendering performance? After a little digging, it seems that directive templates do have a negative impact on rendering; but, that impact is very small.
Run this demo in my JavaScript Demos project on GitHub.
For this experiment, I'm going to render two different lists - friends and enemies - using ngRepeat. I use ngRepeat for these kinds of experiments because the ngRepeat directive is really where performance bottlenecks become symptomatic; your bindings don't really grow out-of-control until you start iterating over datasets.
In the first version of the code, the content of the ngRepeat directive will be duplicated inline. In the second version of the code, the content of the ngRepeat will be moved into a new directive - bnPerson - so we can remove the duplication.
First, the inline version:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Directive Templates Have A Small Impact On Performance In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Directive Templates Have A Small Impact On Performance In AngularJS
</h1>
<h2>
Duplicating Inline Code
</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. -->
<script id="list.htm" type="text/ng-template">
<div class="friends">
<h2>
Friends
</h2>
<ul>
<li ng-repeat="friend in friends track by friend.id">
{{ friend.id }} — {{ friend.name }}
</li>
</ul>
</div>
<div class="enemies">
<h2>
Enemies
</h2>
<ul>
<li ng-repeat="enemy in enemies track by enemy.id">
{{ enemy.id }} — {{ enemy.name }}
</li>
</ul>
</div>
</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, the content of the ngRepeat is inline in both lists:
{{ friend.id }} — {{ friend.name }}
And, while the ngRepeat item is different (friend vs. enemy), the content is basically the same. So much so that we can factor it out into a directive that renders the ngRepeat using a template.
In this version, we're going to add the bnPerson directive to the ngRepeat element. This directive will take a scope reference (friend vs. enemy) so that the content can be unified:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Directive Templates Have A Small Impact On Performance In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Directive Templates Have A Small Impact On Performance In AngularJS
</h1>
<h2>
Using Directive Template To Reduce Duplication
</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. -->
<script id="list.htm" type="text/ng-template">
<div class="friends">
<h2>
Friends
</h2>
<ul>
<li
ng-repeat="friend in friends track by friend.id"
bn-person="friend">
<!-- Content supplied by bnPerson directive template. -->
</li>
</ul>
</div>
<div class="enemies">
<h2>
Enemies
</h2>
<ul>
<li
ng-repeat="enemy in enemies track by enemy.id"
bn-person="enemy">
<!-- Content supplied by bnPerson directive template. -->
</li>
</ul>
</div>
</script>
<!-- I am the template used to render the bnPerson directive. -->
<script id="person.htm" type="text/ng-template">
{{ person.id }} — {{ 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 );
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide the layout and behavior for a "Person".
app.directive(
"bnPerson",
function() {
// I link the JavaScript events to the local scope.
function link( scope, element, attributes ) {
// Watch for changes to the linked scope value and assign to person
// as needed.
// --
// NOTE: This is what isolate-scope does behing the scenes to
// implement two-way data binding... at least partially; only w're
// doing it without having to alter the scope tree. The trade-off
// is that we populate the "parent scope" with a new value.
scope.$watch(
attributes.bnPerson,
function handlePersonBindingChangeEvent( newValue ) {
scope.person = newValue;
}
);
}
// Return the isolate-scope directive configuration.
return({
link: link,
templateUrl: "person.htm"
});
}
);
</script>
</body>
</html>
As you can see, the repetition has been factored out into a directive template. This keeps the code more DRY; but, it does have a small impact on performance (see the video above). That said, the impact is very small and should probably not be seen as a contraindication for directive usage.
Want to use code from this post? Check out the license.
Reader Comments
@All,
As a follow-up, I wanted compare the use of directive templates to the of ngInclude inside an ngRepeat:
www.bennadel.com/blog/2738-using-ngrepeat-with-nginclude-hurts-performance-in-angularjs.htm
Using ngInclude has a significant performance impact due to the way it compiles code in its linking phase.