Home » Angularjs » angularjs ng-model inside ng-repeat has poor performance

angularjs ng-model inside ng-repeat has poor performance

Posted by: admin November 30, 2017 Leave a comment

Questions:

I have a performance problem with angular in the following scenario:

<div ng-repeat="a in array">
  <input ng-model="something">
</div>

I wrote code in my controller that on ng-click changes the array to have a different set of objects. The problem is that if the array has a decent amount of objects, the click is not as responsive as I would like it to be (short delay).

After some investigation, I noticed that the $digest takes a pretty long time after I change the array in my ng-click. So I created this short test code to reproduce it.

The real app scenario is this: I have a table in which every row represents an editable object and each object has many different fields I want to be able to edit. This way, whenever I click on a row in the table, there is another html that has all those ng-repeats with different inputs on the properties of my object.

Does anyone have an idea on how to make this more efficient?

Thanks

Answers:

It is generally a bad idea to have too many input elements on the same page. This is why professional data grid editors opt for editing only a single data row at a time either in a separate pop-up window or in-line. Even when it is used in-line, the objects are injected on-the-fly.

An input element is simply too heavy to have too many of them on the same page. I have done the same mistakes in the past, trying to implement a data grid where all editable fields were input elements from the beginning. On top of that, you have to keep a live angular model binding, which adds a performance overhead.

One of the easiest ways to do it in your case is to implement a directive that displays as a span element until it is clicked and swap for an input element on click event. Another alternative – have both and toggle their visibility style. The latter is probably even easier from within angular directive, but not as efficient perhaps.

Also keep an eye on other bindings that you have. When it comes to data grids this becomes important. In Angular 1.3 you can now use “::” syntax for one-time bindings, which also may help.

Questions:
Answers:

If I understand your question correctly, you are experiencing ui blockage that makes your experience choppy. How many items are in your collection? If I’m not mistaken ng-repeat is synchronous as can be seen here:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L361

So you simply will not be able to render hundreds/thousands/etc of records without blocking the UI with the present way ng-repeat is implemented. The only solution is to go with an asynchronous solution and render a little bit of the collection at a time. Unfortunately I haven’t seen an asynch ng-repeat (one may exist, you should look for it or implement it yourself and give it to us).

Fortunately you can use the limitTo property of ng-repeat as a hack for something similar. If you have limitTo:num where num = 5 then only the first 5 records will be rendered. If you set num = 7 then it keeps the 5 already rendered records and only the 6th records and 7th record are rendered.

So to render a few thousand records, you need something like this that changes the limitTo asynchronously:

<div ng-repeat="a in array | limitTo:tick">

var repeatAsyncHack = function() {
    $scope.$digest();
    sum += +new Date() - t;
    if($scope.tick < numOfObjects) {
        $scope.tick+=tickAmount;
        requestAnimationFrame(repeatAsyncHack);
    } else {
        $('#results').prepend(
            $("<div>" + 
              numOfObjects + ' objects test took: ' + sum +
              "ms</div>"));
    }
}

I updated your demo at http://jsfiddle.net/qrJG3/15/

Of course there are cons to this as the total render time is much longer. Doing this is just a trade off to make the app more responsive after all.

Please correct me if I misunderstood what you were getting at.

Questions:
Answers:

Well if you are creating DOM elements on the fly there will be a delay.

If the elements are already on the page and you just hide them until their time it will usually be much faster.

Another aspect you could work with is the felt responsiveness and the real responsiveness – you could first show some static html and then replace it afterwards with the actual elements. The user would get immediate results while the real results are created.

Edit

Lets compare the generated HTML with and without ng-model

With model

<div ng-repeat="a in array" class="ng-scope">
   <input ng-model="a.qqqq" class="ng-pristine ng-valid">
</div>

Plus eventhandler on the input.

Without model

<div ng-repeat="a in array" class="ng-scope">
     <input>
</div>

And no eventhandler.

You can do a deep research using the debugging version of Angular to actually see whats done when adding a ng-model.

-> The reason why it takes more time is obvious – Angular is doing more.