Understanding Watchers in AngularJS

Understanding Watchers in AngularJS

December 22, 2016

Anyone whose used Angular 1 knows how easy it makes two-way data binding. By simply throwing ng-model on an input tag, the view is able to dynamically update it's underlying data model. This provides a great advantage to developers as the presentation layer is directly linked to the app's state. Developers no longer have to worry about callbacks, event handlers, or service calls to keep views in sync with the data they represent.

While such functionality is standard with Angular 1, it can severely hinder performance if you don't know what's going on under the hood. In fact, Angular 2 takes a fundamentally different approach to data binding because of the issues seen with Angular 1's performance.

This is not to say that Angular 1 shouldn't be used. The framework is still widely used and growing in popularity. If you take advantage of Angular's two-way data binding however, it's important to understand how this magic really works. This article explores AngularJS $$watchers and how they work with the Angular digest cycle to keep views constantly synced with data models.

What are watchers?

Any time you use a built-in Angular directives (such as ng-show, ng-if, ng-repeat), you are creating a watcher. Take the following code bit:

<div ng-show='isMine'>
</div>

By using Angular's ng-show directive, you create a watcher for the parent $scope object. In this case, your app is now 'watching' for changes to the scope's isMine variable. Similarly, if you have:

<div ng-show='isMine'>
{{myVar}}
</div>

then there is an additional watcher for the myVar scope variable. Any time you reference a scope variable in an ng-* or use the double brackets {{ }} you create another watcher for the scope.

The digest cycle

So what? Why do watchers matter in our Angular apps? The answer is found in Angular's digest cycle. This cycle is really a function that repeatedly checks for changes in the 'watchers' we've defined for a given scope. With every watcher you create, a corresponding watcher function is added to the digest loop. This function executes every time the cycle runs, comparing the watcher's old value with it's new value. The cycle runs once to check for initial changes, and then a second,third,fourth time until all watchers stop changing.

The digest cycle is what makes two-way data binding possible in Angular 1. It's also what causes the majority of Angular 1's performance issues. It should be noted that the digest cycle runs every time the app's state changes. You may see the $digest() or $apply() functions used. These manually trigger the digest cycle, however Angular based DOM manipulations, AJAX calls, and callbacks automatically fire the cycle as well.

How many watchers is too many?

When considering how often the digest cycle runs, it's easy to see how watchers can slow your app down. This doesn't mean you shouldn't use watchers. Besides, what's the point of using the Angular library if you can't take advantage of the two-way data binding?

As a general rule, you should try keeping the number of watchers for any given view in the hundreds. Remember that this will include not only the current scope but any children scopes as well. A few hundred watchers may seem like a lot, but consider the following:

<div ng-repeat='row in table'>
{{row}}
</div>

Not only are we defining a watcher with ng-repeat, we are also defining a watcher for every iteration of the {{row}} expression. This means that with just 100 data table rows we would already have 100+ watchers for this scope!

Conclusion

While watchers can get out of hand fast, you'd be surprised as to how many watchers you can have without impacting performance. This is not to say that excessive watchers won't slow you down. Be especially careful with nested ng-repeat's when using Angular 1's two-way data binding. Also remember that frameworks like Angular 2 and React take more efficient approaches to data binding and are worth exploring for future projects.


Join the conversation...



Submit

cvvv
Posted by vcbvb on May 16, 2017
this article does a great job of explaining why angular 1 should never be used. while two-way data binding used to be the talk of the town, front end guys are starting to get wise with react, angular 2, etc. don't use angular 1...
Posted by Anonymous on May 11, 2017
watchers are one of the hardest things to understand with Angular 1. they aren't as expensive as people typically think..browsers are powerful things these days. it is true however that for every $scope variable you define, you are going to generate a watcher. When you add these up (especially with pages rendering thousands of records via ng-repeat etc) you are going to start running into performance issues. As pointed out in the article, Angular 2 addresses this and should be used moving forward. angular 1 is a great way to get started with the SPA world though.
Posted by Kumariv K. on May 1, 2017
once you realize what watchers are doing you will realize that Angular 1 is not the right way to do things these days..frameworks like React and Angular 2 are following more of the flux (one way data flow) architecture and abandoning classic MVC..this is making front end SPA's much better performance wise.
Posted by Terril on April 19, 2017
once you fully understand how watchers work in Angular 1, you will realize why Angular 1 is quickly becoming a thing of the past...
Posted by Robbie Eisenhower on March 18, 2017
$watchers are a very inefficient way to handle change control. moving forward look at react and Angular 2. they both make great use of the shadow DOM and completely eliminate the need for digest cycle, watchers etc.
Posted by Timmy Vanderkamp on March 16, 2017
agreed. a great explanation on AngularJS watchers and what they are all about...
Posted by Tom Berbank on December 22, 2016
great read!
Posted by Sam Erickson on December 22, 2016


More by the stackchief