When ngOnChanges is not enough

Introduction

“ngOnChanges” is a component life cycle hook that can be used to watch component inputs and execute some operations in case these inputs change. Even though this hook is useful in 80% of the case, it has an important limitation.

In this article, we’ll take a use case where “ngOnChanges” is just not enough and will see how to achieve what we want using “ngDoCheck”.

Use case

The use case is pretty simple, we want to create a “data-table” component used to display a sorted array of number. Of course, the idea is that the input passed to this component won’t necessarily be sorted, it will be up to our component to sort it. The challenge here is that the list must be maintained sorted, whatever what happens. For example, if a number gets added to the original list, the visual result must still be sorted.

Let’s start by the “app” component:

Nothing special here. Just an array of number and two functions used to add a random number to the list or edit the first one. On the template side, nothing fancy neither:

Now it’s time to implement our “data-table” component. Let’s try doing so by using “ngOnChanges” to detect a change in the input and sort the table.

And the template:

When you look at the result, it should not come as a surprise to you that the list is indeed sorted:

ngDoCheck01

However, if you click on “Add”, you’ll see that the result is not the one you expected:

ngDoCheck02

Indeed, the added number is simply added at the end of the list even though we are watching the data input in the “data-table” component.

This comes from the fact that Angular is using the strict equality operator (===) to detect whether an input has changed or not. So in the case of primitive, it’s easy, if the input was “3”, then gets updated to “5”, then “3” !== “5”, so “ngOnChanges” gets fired.

However, in the case of an array, it’s a bit different. Indeed, even though an item is added to the array, the reference of this array stay unchanged, so “ngOnChanges” does not get fired.

That’s in that kind of situation that “ngDoCheck” becomes handy. This life cycle hook is executed as soon as Angular performs a check to detect whether something has changed in the application. Therefore, we could use this hook to check whether or not the input has changed.

An easy way could be to store the length of the array in a variable and in “ngDoCheck”, check whether this length has changed or not and if so, sort the array. This would work in case of item addition/removal but not in case of item modification. Indeed, when an item gets modified, the length of the array stays the same.

Therefore, we’ll use a class called “IterableDiffers” used to detect changes in array (not only array but that’s what interests us today). Here’s the new code of the “data-table” component:

We start by injecting an instance of the class “IterableDiffers” in the constructor before using it in the “ngOnInit” hook to get a reference to an “IterableDiffer<number>” object. Basically, the “find” method tries to find an appropriate “differ” for the object passed in parameter, then “create” instantiates a new object of that type.

Then, you just have to call the “diff” method on that “differ” in “DoCheck” to ask Angular whether the last “snapshot” of the object differs from the one passed in parameter (“this.data”). If there is no changes, “changes” would be “null”. If there are, “changes” would contain a list of all the changes (items that have been added, items that have been removed, etc…). We won’t go into the details of “IterableDiffers” in this article as it’s not the goal (but don’t hesitate to ask in the comments section if you want a dedicated article).

Let’s test again our application by clicking on the “Add” button:

ngDoCheck03

We can see that the new number (50) has been inserted at the correct position (OK… In reality, it has been pushed then the array got sorted by anyway…). You can also try clicking on “Edit” to see that the array get sorted again when an items changes:

ngDoCheck04.png

5 has been changed in 75 and got sent at the end of the array after the sort.

Conclusion

Event though “ngOnChanges” is a perfect fit for most of the cases, it is sometimes not enough. In that case, you can use the hook “ngDoCheck” that is fired whenever a change detection cycle is performed by Angular.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.