It is not uncommon to have an Angular directive or component that needs to perform some work when its bounded inputs are changed.
And we all know watches are bad for performance, and that you should only use them when you really need them. But sometimes your code really needs them. What to do?
With Angular 1.5’s introduction of components, and the back-porting of lifecycle hooks from Angular 2, we have cleaner ways of achieving this.
Note: This post will use components, but lifecycle hooks are available in Angular’s directive as well. You can make use of this technique even if your team hasn’t moved to components yet, as long as you’re using Angular 1.5 or later.
Let’s look at a an example component. For brevity I’ll be using ES6’s arrow functions, but of course you don’t have to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
As you can see, this is a very basic component that wraps around some native D3 code to render a chart.
Whenever its input binding,
dateSeries, is changed the component re-renders the chart.
And it keeps track of those changes using
Now, let’s make use of the
$onChanges lifecycle hook.
$onChanges is called automatically by Angular whenever an input binding is changed by the component’s parent.
A couple of important details to notice:
$onChanges only works with one-way bindings (and
@ bindings), which is what you should be using 99% of the time, and
$onChanges is only triggered when the parent component reassigns the value.
It will not be triggered if you reassign it inside the component itself.
So let us update our component to use one way bindings and
$onChanges instead of a watch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
That’s about it.
We no longer need to inject
$scope, which is always a good thing.
And we also removed a watch: Angular has its own watch on the binding anyway in order to sync it between components, and we’re taking advantage of it.
Detecting the Initialization Call
Sometimes when using
$watch we would like to treat the first time it is called differently, since
$watch triggers immediately after starting a watch.
The way we identify it would be to write code such as this:
1 2 3 4 5
As you can see, we’d check if
newValue is the same as
oldValue, which is Angular’s way of telling us it’s the initial run of the watcher.
$onChanges we have a clearer way of achieving this:
1 2 3 4 5
As you can see, the
changes object comes with a handy
Keeping Track of the Previous Value
Another useful capability of
$watch is that whenever it was triggered it would supply our listener with both the current value and the previous value.
This allows the code to compare them:
1 2 3 4 5 6
Fear not, the
changes object still got you covered:
1 2 3 4 5 6 7 8
It just so happens that Angular triggers the initial
$onChanges right before calling the
You should be aware of that when you write your component’s lifecycle hooks and make sure that you don’t rely in
$onChanges on anything that gets setup by
$onInit, and if so, make sure to account for it on the first change call.
You just got rid of some needless
Better performance, and modern code – win!
Pat yourself on the back for me.
“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.