Understanding Watchers in AngularJS

Update: Watchers are important to understand because they represent such a major inefficiency in the original AngularJS.

It's because of watchers that the new Angular surfaced.

It's highly recommended that you start using the newer version of Angular. Check out these to get a quick taste for how Angular works today.

The 5 Minute Guide to Making HTTP Requests in Angular

Angular Form Examples


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.

Your thoughts?