6

In the discussion below this article there's a comment by Renan Cakirerk to the effect that, according to an Angular developer, Angular UI performance might degrade beyond 2000 data-bound objects.

It made me seriously consider whether pursuing my non-trivial app with Angular is a good idea. A good app is a fast app after all. I don't want to invest months building something to be bitten at the end.

I am interested in hearing from Angular non-trivial app builders about

  • general strategies people have successfully used for dealing with performance degradation
  • specific strategies based on my requirements and design pattern (below)
  • whether I should abandon Angular altogether at this early stage in the project to avoid a looming "grind to halt"

It's too much of a risk to wait for the possible "ES6 power and perf boons like Object.observe" and future versions that will might developers more fine-grained control on the $apply / $digest cycle so that $scope-limited dirty-checking can be triggered" (Brian Frichette mentions these in the same discussion). I want to know that complex apps can be fast today on v1.2.15.

More details about my problem/solution...

I'm building an app with very rich functionality, where each object (eg user) has many functions that can be done to it, eg linking them to other users, changing their properties, sending them messages, etc.

The spec has upwards of 20 functions on this object: droppable zones, context sensitive toolbar icons (eg the way Word has mini-toolbars that appear near the mouse when you select some text).

These options need to hide and show based on certain mouse actions, like hovering and dragging, and depend on the state of the particular user object (many icons and drop options will show in some circumstances and not others)

Now, the way I've started building this is to have each individual icon and drop area, drag handle, etc as a separate data-bound element with an ng-show (or similar) that's keyed into our custom business logic.

Eg

<user>
  <menuicon1 ng-show="business-logic1"/>
  <menuicon2 ng-show="business-logic2"/>
  <dropzone1 ng-show="business-logic3"/>
  <draghandle ng-show="business-logic4"/>
  <changessavedicon ng-show="business-logic5"/>
  .....
</user>

Assuming the 2000 theoretical limit above is to be feared, then 20 custom showable hideable bits means 100 users (shown using the amazing ng-repeat) is my limit! Maybe showing 100 is silly and I can attack this with filtering etc, but it seems to me that dividing by 20 drastically reduces my object "bandwidth". And what happens when the boss wishes to add 10 more functions?

If I were doing this the jQuery way, I'd probably construct and destroy many of the icons and menu items as needed. Slightly less responsive per hover/drag, but at least the app can scale the number of objects that way.

3
  • I'm suggesting the following based on what you said you would do if you were doing it the 'jQuery' way. The ng-if directive can be used instead of ng-show, so that things are not in the DOM if they aren't being shown - sorry that I can't be of more help Commented Mar 29, 2014 at 1:54
  • 1
    Also, read this question for more details: stackoverflow.com/questions/9682092/databinding-in-angularjs/… Commented Mar 29, 2014 at 2:02
  • Great link, thanks link64! MW's comment there is very relevant :) Commented Mar 29, 2014 at 17:03

4 Answers 4

4

I have encountered the somewhat infamous ng-repeat performance issue. I have a table with about 10 columns which consists of a row for each day. If I try:

<tr ng-repeat="row in rows">
  <td ng-repeat="column in columns">
    {{ row[column.id] }}
  </td>
</tr>

I run into performance issues after creating on the order of 100-200 days.

But the truth is, there are a ton of stupid things about this approach. For one, no display can show this many rows. Why add a bunch of crap to the screen that isn't going to be rendered? Also, as others have made apparent, it's unlikely that a user can meaningfully interact with enough items on a page to warrant 2k bindings.

I was going to go with some complicated pre-rendering of rows or even the whole table that would compile all of the html and then update every time there was a change to the table data. But then I had some little widgets in the rows that I wanted to have bindings on.

So instead, I went with http://binarymuse.github.io/ngInfiniteScroll/ It makes it super easy to add or remove items on the fly from your page. Now I can show 40ish rows at any time, and when the user scrolls in either direction it will append the new rows and get rid of the old. I had to do some modifications to implement the remove elements part, but for me it's by far the best option because it allows me to stick with the angularjs mentality which I really like and still get good performance.

It seems to me that your issue can be fairly trivially solved with an ng-if instead of ng-show. If you remove the elements from the DOM, there shouldn't be any more issue with bindings for that item.

Should be easy enough to test with what you have and some extra ng-repeats if necessary though right? It's super easy to plug in infinite-scroll to give it a whirl.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks! A few responses which I'll separate into different comments (a) I'll have a play with ngInfiniteScroll, but are there any examples of your approach? Only the basic ngInfiniteScroll example is working right now and it doesn't do any tear down of already viewed items...
(b) Thanks for the steer on ngIf! :) Sounds like a similar effect to the jQuery approach I mentioned. But aren't watches still deployed on every ngIf, and isn't that the concern? Or is Angular smart enough to ignore elements nested inside the ngIfed element so that they aren't "data-bound" until the ng-if evaluates true? Eg in <displayedusericon><toolbar ng-if="showonhover"><toolbaritem1 ng-if="bus-logic1"/>...</toolbar></displayedusericon> is anything inside <toolbar> not "data-bound" until it's hovered over? If so then I believe that will address my concern.
(c) "it's unlikely that a user can meaningfully interact with enough items on a page to warrant 2k bindings": no, true, my concern was all the functionality that's hidden from the user, until it springs into life upon hover/drag/click/etc. Eg Gmail chat bar: Hover over person's name ==> box pops up with bigger photo, video/chat icons and "Add to circles"; hover over "Add to circles" ==> list of circles, and so on. As I said in (b), if, using ng-if all the detail inside the "pop up" isn't being "watched" until it's hovered, then problem solved.
to self-answer (b) and (c), it looks like ngIf (and ngSwitch too) do exactly what I was hoping. See youtu.be/zyYpHIOrk_Y?t=10m30s
I also love the look of the "fast bind on notify" starting here youtu.be/zyYpHIOrk_Y?t=12m30s
|
1

You probably want to find the true bottleneck first before applying any optimizations. You can use Chrome DevTools with code snippets to profile your code, see http://bahmutov.calepin.co/improving-angular-web-app-performance-example.html

Comments

0

We also run into performance issues, UI started to stuck, it took a lot of time to data-bound object, after some tests we realized that ngRepeat adds $watch to each element and as a result has a direct impact on performance.

We decided that the best approach is to work with the DOM ourselves, we created our own ngRepeat directive and iterate the elements using the native JavaScript API (or JQuery, it doesn't matter), also we used CreateDocumentFragment for DOM manipulations (why would you want to use CreateDocumentFragment).

Example:

  mainApp.directive("myRepeater", function () {
   var LIST_ITEM = "li";
        return {            
            restrict: "A",
            link: function (scope, element, attrs) {
                var rawElm = element[0];        
                scope.$watch(attrs.source, function(newValue) {
                    if (!newValue || !newValue.length || newValue.length === 0) return;

                    // wipe the previous list
                    rawElm.innerHTML = "";
                    var frag = document.createDocumentFragment();

                    newValue.forEach(function (item) {
                        var listItemNd = document.createElement(LIST_ITEM);
                        var textNd = document.createTextNode("your text");
                        listItemNd.appendChild(textNd);
                        frag.appendChild(listItemNd);
                    });

                    rawElm.appendChild(frag);
                });
            }
        };
    });

1 Comment

As far as I can tell, your solution is good for rendering static data, but doesn't support the repetition of rich Angular components such as the hypothetical <user> object in my question.
-1

The answer in 2014 should have been: Yes, abandon Angular now.

Counter to what urban_racoons says, it's easy to have 100-200 items on a page that the user can see and meaningfully interact with.

Eg Google calendar on the 7 day display has 18 hours of 1/2 hour blocks showing on my monitor. If you had one ng-class for each one, you've got 252 watches right there. But you want ng-clicks, ng-ifs galore if you want all that Google calendar functionality.

On the other hand, in my own app, with only a header, some panels, a left nav menu (~5 items) and detail pane with ~5 tabs and ~30 items in the detail pane, I have >3000 watches (thanks to ng-stats).

Not only that, rendering just those 30 items takes over 2 seconds - Chrome devtools Profiles tab says it's all the controllersBoundTranscludes, nodeLinkFns and ngWatchIfActions (ng-if was supposed to be the saviour, but I have a few per item so time to evaluate those and render the result adds up).

The problem is I'm damned if I do (put ng-ifs everywhere means re-render time when showing the items hidden by the ng-if is slow, and even ng-infinite-scroll is sluggish) and damned if I don't (put ng-shows means the watches add up to unacceptable levels).

So I am having to resort to a bunch of hacks and optimisations, such as the

In my opinion these optimisations should come in the Angular core.

The harder question in 2016, with 1000s of lines of code already written: Should I abandon AngularJS now?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.