/mar 12, 2015

Nuances of two-way Data Binding in AngularJS

By Tyler Waneka

When I first started looking at front end frameworks, all anyone wanted to talk about when it came to AngularJS was two-way data binding. You just connect the Model and the View and when one changes, so does the other. It's magic!

It's one of the more glittery feature of AngularJS, and at times it can be extremely useful. But, what's the cost? There's always a cost. With data-binding, the (potential) cost is a drop in performance.

Let's talk briefly about how AngularJS manages the data binding. If you've been coding in JavaScript for very long, you're probably familiar with the idea of an event loop. It would take a few very long blog posts (or a video) to accurately describe an event loop, but basically it works like this. Behind the JavaScript curtain, there's somewhat of a queue. This queue is waiting for events. JavaScript goes through the queue and tries to execute all the events. When it gets to the bottom of the queue, it goes right back up to the top and repeats the process. So when an event is added to the queue, it will get executed on the next loop around. AngularJS has an event loop that it calls the $digest loop that works more or less the same.

Any time you set up a data-binding, it adds a function to what's called the $watch list. And this $watch list gets resolved in the $digest loop. So the more data-bindings you've got in your app, the longer your $digest loop. There are many more moving parts to the way AngularJS implements the $digest loop, but the point remains the same. You may not see the performance effects when implementing a to-do list in an AngularJS tutorial, but if you're working on an application at scale, the data-bindings can really start to pile up.

But don't worry, it's manageable! There are many things you can do to keep your data binding woes under control, but here are two strategies I find very effective.

Bind Once

Often you need to render some data on the DOM that comes from a Model, but once it's been rendered it is completely static. There is no way for the user to modify the data in the View, and the Model won't be changing so you don't need to update the View. As of version 1.3 of AngularJS, there's a very handy feature available that allows us to render data without binding it back to the model. This means no function is added to the $watch list and therefor we can keep our $digest loop nice and small. Let's check it out.

Typically, you would render a value in the DOM like so:

<div>{{ user.name }}</div>

To handle this without the data-binding, simply update the code as follows:

    <div>{{ ::user.name }}</div>

How easy is that? AngularJS will process the DOM like normal, but then remove the binding from the $watch list and you're left with static content and a happy $digest loop. Those two little colons can help keep your data-binding overhead very minimal.

Control your ng-repeats

Maybe the biggest offender of overloading the $digest loop is ng-repeat. It automatically adds at least one binding for every item in the list, not including any additional data-bindings we might set up on those items. Consider this code:

      <li ng-repeat="friend in friends">
        <div ng-show="friend.attractive">Hot: {{ friend.name }}</div>
        <div ng-show="!friend.attractive">Not: {{ friend.name }}</div>

In addition to being a shallow jerk that categorizes his friends attractiveness, we've also got 5 data-bindings set up for each item in the list. Imagine a list of 200 friends, that's 1000 data-bindings! And this is still a relatively simple ng-repeat implementation; it can certainly get much more complex. I've read that AngularJS starts to slow down at about 2000 data-bindings, which is not to difficult to achieve.

So how do we keep our ng-repeats under control? We must be mindful of how many data-bindings we are setting up for each item. If you need to do some DOM manipulation (such as show/hide), consider doing it inside the link function of a directive using Jquery style DOM selectors and manipulations (.show()/.hide()). If you absolutely must have a lot of data-binding handled in an ng-repeat, you might have to break up the size of the list with pagination, smart infinite scrolling, etc...

To summarize, it pays to be mindful of where you're implementing data-binding. Keep it on your mind as you are building your application. Ask yourself if it's really necessary for that feature you're working on to utilize data-binding. I know we've identified many areas of our application where the data-bindings are not necessary, and I bet you do to.

Happy optimizing!

Related Posts

By Tyler Waneka