Reputation: 4237
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
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.
Upvotes: 6
Views: 2456
Reputation: 4237
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-if
s 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 controllersBoundTransclude
s, nodeLinkFn
s and ngWatchIfAction
s (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-if
s 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-show
s means the watches add up to unacceptable levels).
So I am having to resort to a bunch of hacks and optimisations, such as the
ng-if
/ng-show
hackng-show
with ng-repeat
and disabling all the watches on scopes below it with something like Radek's
techniqueIn 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?
Upvotes: -1
Reputation: 1919
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
Upvotes: 1
Reputation: 6187
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);
});
}
};
});
Upvotes: 0
Reputation: 3499
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.
Upvotes: 4