21

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

sheba
  • 800
  • 6
  • 14
Barnash
  • 2,012
  • 3
  • 19
  • 22
  • create a realistic demo of what you are actually doing. How can anyone make your code more efficient when we can't see any of it? – charlietfl Jul 09 '14 at 11:54
  • 1
    Well, you are right, but my example is way too complicated to be put here and explained, so I tried to narrow it down to the real problem, which is: why is this so much slower than when I remove the ng-model attribute (I understand the overhead, but can't see why it should be so big) – Barnash Jul 10 '14 at 06:01
  • 1
    But if you are seeing visual delays, you are likely doing something that isn't best practice – charlietfl Jul 10 '14 at 11:53
  • I'll try to describe my production code in a nutshell. I have a canvas with objects you can select and drag, and this canvas is bounded in some way to angular. When you start dragging an object and the cpu takes even 50 ms to run, you get a small lag in your app, you feel it much more than you would feel it if it was a click. The reason it happens is because when I start the drag - the object get's selected (by setting a variable in the scope) and another directive that watches this object start rendering itself (which brings me to the questions I asked), Continuing in another comment... – Barnash Jul 10 '14 at 12:39
  • After narrowing down why after the select, the digest loop takes so much time, I came into this short example that shows the root cause of the delays. Now, I can do many things that will live with this angular overhead and still manage my application (like avoiding the selection before drag, or no show all the input fields in the directive), but I think there is something fundamentally wrong with angular that it takes too much overhead to do these simple actions, and that's why I posted this question. – Barnash Jul 10 '14 at 12:42
  • 1
    Have you tried using batarang on Chrome to better idea about why it takes so long? You can profile performance and see which specific tasks are taking longer than others. – markthethomas Jul 11 '14 at 23:05
  • I tried with the whole app, it was too messy, but it's probably a good idea to try Batarang on the small example I wrote here. Excellent comment. Thanks! – Barnash Jul 13 '14 at 06:25
  • @OP Looking at your code sample, I see you're pushing your objects int your array one at a time. If I recall correctly, this will trigger digest for each push in response to the model changing. A similar thing occurs in other frameworks like KnockoutJS. This can be avoided by setting the array all at once: $scope.array = newArray;. Only one digest should result. – m.casey Jul 15 '14 at 23:17
  • @m.casey Thanks, but it doesn't work like that in Angular. $digest is not called when I push an item to an array. Specifically in my example it doesn't matter, since I only measure my own call the $digest method and if other stuff takes a lot of time, I just don't measure how much time they took. – Barnash Jul 16 '14 at 08:02
  • If you want to improve ng-repeat rendering speed, I think you can read this blog - http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ . There are also links at the end of article that talk about track by in angularjs ng-repeat which improve re rendering. – hutingung Jul 19 '14 at 03:06
  • If you want a better UX, there is another question asked - http://stackoverflow.com/questions/17348058/how-to-improve-performance-of-ngrepeat-over-a-huge-dataset-angular-js . IMHO, I think you must related it to real life problem then you can find the right solution. – hutingung Jul 19 '14 at 03:08

3 Answers3

3

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.

David
  • 251
  • 2
  • 5
3

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.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
  • These options seem a good way to tackle the performance issues. However, you're ommiting the fact that you will not be able to jump from input to input by pressing `tab` or `enter`, which - especially in this case with many inputs, is essential. – frhd Oct 07 '15 at 09:30
  • @frhd That's why some libraries implement this manually. It is possible to handle Tab on the window level and redirect to your custom controls. – vitaly-t Oct 07 '15 at 12:27
0

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.

Mx.
  • 3,588
  • 2
  • 25
  • 33
  • 2
    Thanks, but creating DOM elements on the fly is not the problem here. If you remove just the `ng-model` in my example you'll see it's working dramatically faster. Check this out: http://jsfiddle.net/c49sL/ – Barnash Jul 09 '14 at 11:31
  • when adding a lot of them it is a problem - often a simple "please wait.." is the only option http://jsfiddle.net/Vfg8L/ – Mx. Jul 09 '14 at 11:36
  • So the problem is that the screen freezes for the time it takes, and I know it might be a problem with too much content, but I think I have a fair amount of content and I don't see a good reason why angular needs to take 300% more time than just adding a normal input element, just seems like too much overhead – Barnash Jul 09 '14 at 11:41
  • I altered my answer - is that a reason why angular needs more time ? – Mx. Jul 09 '14 at 11:51
  • btw - freezing is normal while executing js. you can't really avoid that but you can make it less noticeable http://stackoverflow.com/questions/714942/how-to-stop-intense-javascript-loop-from-freezing-the-browser – Mx. Jul 09 '14 at 11:54
  • 2
    I understand why it needs more time - I just think that having an overhead of 300% is way too much. Especially when angular doesn't have that kind of overhead in many many other things you do with it. – Barnash Jul 09 '14 at 11:55
  • That's the problem with frameworks - often capable of doing much more than you need and because of they are coming with a lot of overhead. – Mx. Jul 09 '14 at 12:06