A Quick Look At How DOM Structure Affects Text Interpolation Watchers In AngularJS
I spend a lot of time thinking about how to improve the performance of my AngularJS apps. Not in a "premature optimization" way - more like in a "customers are complaining" kind of way. As such, I like to stop and noodle on any aspect of the code that may be leveraged for better performance. This morning, I wanted to quickly look at how your DOM (Document Object Model) structure affects text interpolation in AngularJS and how text interpolation affects the number of $watch() bindings on the page.
Run this demo in my JavaScript Demos project on GitHub.
When AngularJS compiles your HTML, it iterates over the DOM structure and looks for text nodes that contain the interpolation syntax markers. When it finds them, it computes an interpolation function and then connects the interpolation function to the current scope using a $watch() binding. This means that the structure of your DOM will have a direct affect on the number of overall $watch() bindings that are active on your current page.
To see this in action, I've created two micro AngularJS apps. Both of them produce the same exact output; but, the underlying DOM structure is different (in ways that might not seem relevant at first). For each, I'm going to alert() the number of active $watch() bindings on the page.
First, the "simple" DOM structure:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
A Quick Look At How DOM Structure Affects Text Interpolation Watchers In AngularJS
</title>
</head>
<body ng-controller="AppController">
<h1>
A Quick Look At How DOM Structure Affects Text Interpolation Watchers In AngularJS
</h1>
<p>
<a href="javascript:alert('Watch%20Count:%20'+getWatchCount());void(0);">Get $watch Count</a>
</p>
<p>
My friend, {{ friend.name }}, loves being a {{ friend.profession }}.
I've only known her for {{ friend.friendship }} years; but, she's been doing
it for {{ friend.workHistory }} years.
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.22.min.js"></script>
<script type="text/javascript" src="./get-watch-count.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 ) {
$scope.friend = {
name: "Tricia",
profession: "Stunt-man",
friendship: 5,
workHistory: 15
};
}
);
</script>
</body>
</html>
As you can see, all of the text interpolation is contained inside a single P-tag. And, when we alert the number of watchers, we get 1. That's because the entire P-tag can be compiled down and linked with a single interpolation function.
Now let's look at a more complex version:
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
A Quick Look At How DOM Structure Affects Text Interpolation Watchers In AngularJS
</title>
</head>
<body ng-controller="AppController">
<h1>
A Quick Look At How DOM Structure Affects Text Interpolation Watchers In AngularJS
</h1>
<p>
<a href="javascript:alert('Watch%20Count:%20'+getWatchCount());void(0);">Get $watch Count</a>
</p>
<p>
My friend, <span class="name">{{ friend.name }}</span>,
loves being a <span class="profession">{{ friend.profession }}</span>.
<!--
TODO: This is a really misleading variable name - it's not so much
a friendship entity as it is just a duration for a friendship.
Consider renaming.
-->
I've only known her for {{ friend.friendship }} years;
<!-- TODO: Fix another horrible variable name. -->
but, she's been doing it for {{ friend.workHistory }} years.
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/jquery/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.22.min.js"></script>
<script type="text/javascript" src="./get-watch-count.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 ) {
$scope.friend = {
name: "Tricia",
profession: "Stunt-man",
friendship: 5,
workHistory: 15
};
}
);
</script>
</body>
</html>
This time, we're still inside a single P-tag; but, there are nested structures. Not only are two of the variables inside Span tags but, there are HTML comments in between several of the other variables. Both of these characteristics affect the number of watch bindings. And, in fact, when we alert the number of watchers, we get 4.
By doing nothing but altering the structure of our DOM, we've quadrupled the number of watchers on the page. On its own, this isn't a problem. But, we do know that the number of watchers can affect AngularJS performance at scale. And, we also know that the complexity of the DOM tree can affect the performance of web pages in general. As such, it's just one more thing we can think about when we're trying to squeeze better performance out of our AngularJS applications.
Want to use code from this post? Check out the license.
Reader Comments
Hi,
slightly offtopic, but may I ask you what kind of software you use for recording your videos?? :)
Thx
@Juri,
No problem - I use Camtasia for Mac (though they have a Windows version as well). It's been pretty cool. I don't do any editing or anything, but it has that capability as well.
Yea...and to be honest, it's a bit scary (especially to Angular newbies) that comments in the DOM reduce the performance of your app (although obviously you could have them stripe out by some preprocessor...but still).
@Juri,
It does seem a little strange; though, I think it's probably more interesting from a "technical" standpoint than from a "practical" standpoint. Or, at least one hopes.
Thanks for the video and the article. When all expressions are in <p>, what about the size of the expression being watched as opposed to number of watches? Is it the case that Angular creates watch for entire <p> instead of just {{ friend.name }} meaning that the whole, potentially long text will be stored in memory?
If thats the case then introducing <span> tags may help in delimiting the text size thats being watched.