Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Angular use DOM diffing or must it "re-render" every list item?

I'm new to Angular, but I've already heard (and read) some "rumors" about its rendering model and how it is different from for example React.

I've read some posts of Angular experts who claim that if you have to render long lists with Angular, it could be slow because Angular would re-render the whole list if something changes, while React (for example) "won't re-render the whole list from scratch once it has already been rendered but it will keep track of rendered DOM elements internally and upon new invocation create new virtual DOM, compare it to the previous one and only apply the changes"

(quoted from a random blog post about Angular rendering issues)

So as I started learning Angular, my first thing was trying this out.

And it seems that I cannot reproduce the problem...

Here is a dummy Plunker which I created to reproduce the issue.

You can add new items to a list of messages which is rendered with ng-repeat like this:

<table>
  <tr ng-repeat="m in messages" class="{{name}}">
    <td>{{m.message}}</td>
    <td>{{m.date}}</td>
  </tr>
</table>

You can click on a button which could update one such item, and you can update another property which is totally unrelated to the list (name)

Now if I open developer toolbar, and modify an item's HTML attributes inside the table and then I click on "add a new message", my modifications don't get lost or overwritten by Angular - it seems that Angular doesn't re-render the DOM fully. It seems to be pretty smart.

Has Angular started to use DOM diffing lately? (My demo uses Angular 1.4.0 beta)

Just because from the DOM rendering point of view, I just don't see the big difference between React and Angular.

Could you show me a use case which show the drawback of Angular's rendering model?

like image 351
Zsolt Avatar asked Mar 16 '15 00:03

Zsolt


1 Answers

There is quite a bit of confusion regarding this topic, mostly because it's not a simplistic "angular re-renders everything" type answer.

The basis of angular data binding (in 1.x releases) surrounds around the concept of a $digest loop and $scope. $scope is a special object which "self tracks" it's properties, and creates a JavaScript event listener for each property, using the method $scope.$watch(). These listeners monitor changes to the changeable input elements in HTML, and to the $scope properties.

Whenever any JavaScript listener fires, the $digest loop cycles through every item under $watch and updates the values appropriately. You also can call $scope.$apply() to manually execute a $digest loop. This loop can execute multiple times, as changes to one value can affect another value under $watch which triggers another $digest. The $digest loop does have an iteration cap to ensure it stops on circular references, however.

The rub comes in when you are dealing with arrays of objects, and the special directive ng-repeat. By default, the $watch() function only checks object reference equality. Within each $digest, AngularJS will check to see if the new and old values are the same "physical" object, and will only invoke its handler if you actually change the underlying object reference.

To counter this, ng-repeat creates it's own unique scope. This allows for a unique $watch for every element in the array. The challenge here is that if the array itself changes, then this unique scope is regenerated, along with all the $watch elements. Pushing, Popping, Splicing an array can create many $watch values.

Angular provides a few ways to deal with this issue.

The new Bind Once syntax added in 1.3 allows for Listeners which only exist until the expression is evaluated. ng-repeat="element in ::elements" Will iterate through the array, populate the DOM, then destroy the Event Listener. This is ideal for situations where the DOM element does not change once evaluated.

It is also possible to aggressively track elements, using track by. ng-repeat="element in elements track by $id" will create a $watch on the unique $id value, rather than the element's position in the array. This allows for more stable $watch propagation, and is ideal in cases where the value of the element may change.

As to why the changes you made in the console were not lost: Firstly, changes in the developer console are not going to trigger any Event Listeners. Secondly, the specific DOM element you changed would only be modified if the $watch detected a change. This is not really a 'Diff' of the HTML, however; Angular isn't "watching the HTML", it is "watching the data", so to speak.

like image 99
Claies Avatar answered Nov 01 '22 04:11

Claies