You can only go so long in your Angular path before starting to learn about performance in Angular and some of its common problems.
You’ve probably heard about “lots of watches” being a problem. But what exactly is a “watch”? Is it just $scope.$watch()
? No! Let’s dig in.
What’s in a Watch
A “watch” is Angular’s mechanism for keeping track of a specific expression and acting when it changes. Every watch has the expression that it evaluates on every digest cycle to see if it has changed and also the handler function that gets called whenever the expression’s value has changed.
Not all watches were created equal, especially from a performance perspective. By defaults watches are shallow and so pretty quick. But if, for example, you use a deep watch on a huge list, don’t be surprised if it adds a lag to your app.
The Common Types of Watches
The $watch
Yeah, the obvious case is the one I just mentioned. Whenever you explicitly use $scope.$watch('expr', function() {})
you are adding a watch for the expr
expression.
Templates Binding with {{ foo }}
Whenever you use double curly brackets in a template in order to display a value you’ve set in your scope you’re basically adding a watch. This watch will keep track of the value and update the template’s DOM whenever the value changes (after all there’s no magic here – the browser doesn’t know how to bind your expression and the DOM which means Angular has to update things, it’s just hiding those details from you).
While we’re at it, let me add on a side note that just adding things to your $scope
doesn’t add a watch. It’s only binding to it in some way that adds it.
Isolated Scope Bindings
When you pass to a directive with an isolated scope something on its scope, that too creates a watch.
For example, if you have a directive whose scope definition looks like this scope: {foo: '='}
then Angular will make sure to keep it in sync – if the value changes in your parent scope it will also changed in the directive’s scope. This is achieved by creating a watch for foo
that makes sure to update it inside the directive’s scope whenever the value changes in the parent scope.
Attributes in Templates
When you have Angular expressions used inside attributes in your templates, those are usually creating watches too.
Take ng-show
and ng-hide
as an example you’re probably familiar with. These directives receive an expression and do something depending on whether it evaluates to true or false. This act of tracking the expression’s value is really watching stuff over.
Filters
This is kind of a private case of the previous point, but it’s worth pointing out. Filters keep being evaluated and updated because on every digest, just like any other watch. But they are usually heavier than regular watches and do more complex things, so keep that in mind.
And, Of Course, Code
Along these watches, which are relatively straightforward to find in your code, you should also keep in mind that more watches might be used by the code of other components you may be using.
As an example, our beloved ng-repeat
directive is using watches (specifically a $watchCollection
) in order to update the list it received whenever changes happen. So even if you don’t see an explicit watch in your template, it doesn’t mean that it’s not there.
Watch Out
So, to sum up this post, as you can see watches are pretty much everywhere in Angular land. Next time you’re looking at the number of watches using ng-stats you should be more equipped to understand how that number came to be.
“Maintaining AngularJS feels like Cobol 🤷…”
You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete.
Components? Lifecycle hooks? Controllers are dead?
It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines!
Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.
Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.