I have an AngularJS app with a paged grid (two nested ng-repeat). One page has approximately 25x40 input elements. In the beginning that made 1000 bindings, the paging performance was acceptable.
But then the complexity of page grow: dynamic classes, varying context menues, conditional content for each cell of the grid. And with estimated 6000 bindings (6 per input element) the paging got unusable slow.
My question is: how do I generally approach performance problems in AngularJS?
The obvious first step ist to measure. But the results of the Chrome Profiler do not tell me that much, far from knowing how to proceed.
Self Total Function
-----------------------------------------------------------------
24 ms 2.79 s angular.js:7997 Scope.$digest
1 ms 1 ms controllers.js:365 setViewportData
16 ms 692 ms angular.js:13968 ngRepeatWatch
8 ms 22 ms angular.js:6439 extend.literal
9 ms 1.22 s angular.js:14268 ngSwitchWatchAction
16 ms 45 ms angular.js:12436 ngModelWatch
0 621 ms angular-ui-4.0.js:264 initDateWidget
0 13 ms angular.js:12859 ngClassWatchAction
0 70 ms angular.js:14184 ngStyleWatchAction
1 ms 5 ms angular-ui-4.0.js:261 getOptions
0 16 ms angular.js:579 copy
0 1 ms angular.js:4558 interpolateFnWatchAction
1 ms 2 ms angular.js:5981 token.fn.extend.assign
0 37 ms angular.js:8151 Scope.$eval
1 ms 1 ms angular.js:6137 extend.constant
14 ms 16 ms angular.js:651 equals
1 ms 1 ms angular.js:4939 $interpolate.fn
Aside: is there any chance that 'Object.observe()' will speed up things in the future (ignoring 'initDateWidget', that's obviously a different topic)?
The thing you can do that will speed up your Angular app the most is to reduce those bindings where you can. One way to do this would be to create a directive that built out the table for you with DOM manipulation rather than using ng-repeats. This will reduce the number of overall watches you have to process, and make that $digest a lot faster.
I know it's ugly to do that, but Angular's not really meant to set up 3000+ bindings. Since it does a digest and it's not an observer pattern, it really slows things down have that many set up.
You could even do a hybrid approach, where you still used the ng-repeat, but all of the values were placed in the DOM with straight DOM manipulation from a custom directive, thus avoiding all of the bindings.
If you have not done so, please install the AngularJS Chrome plugin, Batarang, which will help you pinpoint which of your bindings are causing you grief. https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en
As the other answer suggests, what you're looking for is likely a small case of an infinite-scroll setup for your table where the model you bind to is the subset you're displaying onscreen.
The ng-grid component implements this and might be worth looking at to either use it directly or steal the technique. http://angular-ui.github.com/ng-grid/
Resource
This post about angularJS performance on large lists has a nice overview of the options you have for performance tuning.
Above answers (except for the Batarang plugin) are also mentioned within. This is just an overview of the tips in that article.
Reduce data with limitTo (pagination)
One of the more obvious solutions is to reduce the amount of bindings by reducing the number of items in your view. Pagination of data can be done with the limitTo filter on ng-repeat.
Example at How to improve performance of ngRepeat over a huge dataset (angular.js)? That article also has a jsbin example linked.
Also make sure not to use an inline method for data providing since that will be evaluated on every $digest.
<li ng-repeat="item in filteredItems()"> // Bad idea, since very often evaluated.
<li ng-repeat="item in items"> // Way to go!
Remove bindings with bindonce
Another obvious solution is to remove bindings on specific elements. Sure this means that updates won't be reflected in the view anymore.
The bindonce solution does a lot more than just removing the 2 way binding. Basically it waits for the value to be bound once before the binding is removed. Best read for yourself. Check the bindonce project for details.
In the article listed on top there is also information about a pattern working with 2 lists. One for visualisation and one as a data source.
Use ng-grid
Ng-grid has the advantage that it only renders the elements that are currently visible. Read more at http://angular-ui.github.io/ng-grid/.
Similar ng-if removes the hidden elements from the DOM tree completely while ng-show only keeps them in place but hidden. Take in account that ng-if will put a copy of the original (original is key, not the changes that is) element in place when shown again.
Tips for filtering
The article also has some great tips for filtering lists.
Like using ng-show to hide the filtered out elements since this way no sublist has to be created of the data.
And Another technique referred to as "debounce user input". That last option is to wait with the filtering until user has stopped typing. Including a jsfiddle example.
More
More tips can be found in the linked article. There are resources listed there too so that should be a good starting point. The most obvious onces and quick wins are listed here I believe.
Another nice writeup is How does data binding work in AngularJS?
A bit late but maybe this works for you:
https://github.com/Pasvaz/bindonce
You can use it on those binding that are not meant to change so $digest won't process them anymore.
In angular 1.3 and more you can bind once by using :: no need to use other 3 party js
<li ng-repeat="item in :: items">
This is good if the items will not change so you can bind them once
I have encountered performance issues , when the amount of listeners exceded 1000+ in a data grid component.
I solved this issues , using a directive which builds my view using react.js .
the directive exposed an update function .
each time the data changed (in the controller), the update function triggered the directive , and then the react.js engine did the rendering efficiently.
i know its a big overhead to use a second major framework inside of an angular project, and this is not real data binding magic. but its working much faster.
eventually i stopped using angular.js and moved to react.js + FLUX . i think its better but i know its not easy to shift from angular, but its worth it.
Angular directive that uses react.js
Limiting the number of watches can often go a long way. Here is a summary of techniques that are effective for reducing the number of watches
http://www.syntaxsuccess.com/viewarticle/547a8ba2c26c307c614c715e
I had performance issues with ng-grid with large data, it was solved by replacing it with Angular Grid. The demo on it's website shows it managing 100,000 rows easily.
I have wrestled with this for a few weeks. I have found two things have made a substantial difference:
(i) ONE TIME BINDINGS: Use one-time bindings where you can; and
(ii) DEBOUNCE: For input that does not need to me immediately propagated, but can wait 250ms, set a debounce setting. This has made an INCREDIBLE difference to my large ng-repeat table. I cannot emphasise how effective a debounce setting has been. (see here: https://docs.angularjs.org/api/ng/directive/ngModelOptions)
bject.observe() is a proposed mechanism for bringing true data-binding to the browser. It exposes a mechanism for observing changes to objects and arrays, notifying others of mutations made to these objects.
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<title>Object.observe()</title>
<link rel="stylesheet" href="../css/main.css" />
</head>
<body>
<div id="container">
<h1>code_lab_by_shail Object.observe()</h1>
<p>An object <code>o</code> is created and <code>Object.observe()</code> is called on it.</p>
<p>Three changes are made to <code>o</code> and <code>Object.observe()</code> records these changes as shown below.</p>
<p>Use the console to find out what happens if you make further changes to <code>o</code>: it's defined in global scope.</p>
<p>Call <code>Object.unobserve(o, observer)</code> to stop observing changes.</p>
<p id="data" style="font-size: 14px;"></p>
<script src="js/main.js"></script>
View source on GitHub
</div>
<script src="../js/lib/ga.js"></script>
</body>
</html>
You can also improve performance in general by Disabling Debug Data
Related
Sorry, I don't have a live example, I'm looking for performance advice.
I've finally hit my first performance problem with angular, I have a pretty complex UI and in it, I have a directive with about 3 nested repeats that use directives, in every level the directive uses scope: true and using bindToController syntax.
The data source is not that big, but every repeat ends up being between 30-100 watchers (I'm using this snippet to count the watchers)
I'm calling $compile on the top directive and it takes way over a second (!) to show the HTML (it ends up having a few hundred listeners), ng-if and ng-class and other custom directives, it quickly adds up.
I've added one-time bindings wherever I could, but I'm still at way over a 1000 in total and I guess that can slow things down?
I've ran CPU profilers in different browsers, drilled as deep as possible and I never saw my code taking up and significant time, all I see it jquery/angular taking a long long time, but none of the functions inside show any significant self time.
If I open that 800ms elemData.handle, all I see is angular's $scope.eval, compileDirectives and a bunch of other angular stuff in a deep tree.
Could using scope: true be the culprit? Would complex directives perform better if they use an isolate scopes?
Maybe there are advanced methods I don't know to use one-time bindings? Or something between two-way and one-way?
EDIT: for posterity, this is what happened: I managed to replace my isolate scope directives on the inner-most ng-repeat level with simple ng-includes, the functionality is the same, but execution time is 1/1000. I don't think I'll ever really know why the difference was so huge, I never had many items in the ng-repeat, but the code now runs in 1ms :)
Use $destroy events to clean up event watchers
Use one-way-binding everywhere what you can't change manually (ng-repeat, ng-options)
In templates try to avoid functions that will return value. Simple assignment not needed to be compiled but function will run each $digest (ng-if="ctrl.isDataLiading()" slower then ng-if="ctrl.isDataLoaded")
In list items with a lot of ng-repeat avoid filters that will recount and run collection watchers on arrays that is taking great portion of performance - instead use ng-if to remove filtered items
When I was implementing tree structure that can be like expanded to see child nodes (list with + option) I struggled with such problem. Above approach made functionality better but speed did not rise a lot.
In common words all my answer means: angular populates too many watchers, get rid of them
I was wondering whether I could gain any significant performance difference in my Angular app (specifically view processing) if I change ng-app and ng-controller attribute placement from i.e. body to some inner-page block element with much smaller DOM subtree?
Suppose I have a very long page with huge amount of content, but only part of it is Angular powered. Everything else is either server generated which means it's somewhat static from the client-side PoV.
Would it be better to put ng-app/ng-controller on only that subnode where Angular actually executes or would it be the same if I put them on body element of this very long page?
Does Angular process the view only of the sub-DOM where ng-app/ng-controller are defined or does it process the whole DOM anyway?
Is there any proof about this or even Angular documentation?
Theoretically/Potentially? Yes, as New Dev mentions, the bootstrap function will have a larger DOM to run compile on, which would take longer than compiling a smaller tree.
Practically? Probably not. But your best bet is to benchmark your own page.
As an experiment, you can try the following. I generated a simple random DOM and injected it into a JSFiddle with console.time starting at script load, and ending when the controller is ready. There's a small sub-tree beside (as a sibling) a much larger, 5000-node tree.
Here's the fiddle wrapping the entire body: http://jsfiddle.net/gruagq8d/
And here's the fiddle where only the small subset is used: http://jsfiddle.net/h0hod2j8/
For me, running either of those fiddles repeatedly converges on about 260ms.
I've also tried running similar code on real webpage sources, such as StackOverflow itself, and found the same results (however I did not publish any of these because publishing other people's real pages to JSFiddle does not feel proper without permission) - you can try this for yourself pretty trivially.
Admittedly, this doesn't follow great benchmarking methodology, but if wrapping lots of additional DOM nodes caused significant different performance I'd still expect at least some difference in these.
I don't think that the additional compile time is an issue; for small DOMs it's obviously fast, and for large DOM is very relevant compared to the time it takes your browser to build the DOM in the first place.
All said, as mentioned before, your best option is to try to run similar benchmarks with your own page.
EDIT: Modifying the same benchmark to test digest cycles shows that there's no significant difference in those as well:
Wrapping minimal: http://jsfiddle.net/fsyfn1ee/
Wrapping whole DOM: http://jsfiddle.net/04tdwxhp/
ng-app really just specifies the root element on which Angular calls angular.bootstrap. bootstrap starts a "compilation" process, which is to say that it goes over each DOM element in the subtree of the root and collects directives from it and links them.
Right there, you can see the benefit of restricting the app to a smaller subtree of the DOM:
Compilation process is slightly faster
Less directives are compiled (e.g. <input> elements that should not be part of the app are not compiled/linked).
The thing to remember is that it is the $digest cycle that is the significant source of performance problems/opportunities for optimizations - that is, the number of $watchers and how fast the "$watchers" are.
Official docs say that only portion that is wrapped into ng-app directive is compiled.
If the ng-app directive is found then Angular will:
load the module associated with the directive.
create the application
injector compile the DOM treating the ng-app directive as the root of
the compilation. This allows you to tell it to treat only a portion of
the DOM as an Angular application.
This is pretty much expected as Angular allows to have several Angular modules controlling independent parts of one page (requires manual bootstrapping as pointed by Josiah Keller in comments). And their scopes will not interfere.
However adding extra static html (not angular bound) affect performance only at bootstrap. Yes angular has to compile all that elements at bootstrap to learn whether it should deal with them later. But run-time performance is mostly affected by $watchs. Implicit form of creating them is to do bindings. So the more bindings you have overall, the longer each $digest cycle takes. And gives a general feeling of slow app. I've met a sensible threshold of 2k watches for modern browser/CPUs
I am writing new directive that is going to use ng-repeat to create nested child's and more. What I have seen so far is there are all kind of problems using this great tool of angular. You get a great binding to data, but when data increases, you are going to suffer from very low performance and defects hard to be solve. This is making the all directive very hard to maintain and reuse.
So I wanted to ask all of you who have a lot of experience with AngularJS, what are the best things to be aware of in order to use it properly and not doing huge mistakes.
Did you ever think not using it and just make your own loop?
Be glad to hear your points.
Let’s quickly recap how we can use ng-repeats (along with optional filters):
<div ng-repeat="stock in ctrl.stocks | filter:someCondition">
</div>
We can also use ng-repeats to iterate over objects (In case you hadn’t known this):
<div ng-repeat="(key, stock) in ctrl.stocksMap | filter:someOtherCondition">
</div>
where stocksMap is an object map, where the key is the id of the stock, and the value is the individual stock object. One common misunderstanding now is how AngularJS creates and displays the UI, based on this ng-repeat. This is how AngularJS works under the covers (in a nutshell) when you use ng-repeat:
It iterates over each item in the array (or each key, value in the
object)
It runs each item via any filters that are present in our
expression, to check if it should be displayed or not
It calculates a hash value by which it identifies the object (which
is by reference by default)
It checks if it has already created a DOM element for the hash value
previously If so, it reuses it
If not, it creates a DOM element based on the ng-repeat template
All the DOM manipulations are taken and inserted in an optimal
manner into the actual DOM
A watch is added on the array, which triggers step 1 again if the
array undergoes any change
For More information you can refer this LINK
I upgraded my App to AngularJS 1.2 and so also switched to ui-sortable v 1.2.
The sorting is implemented for Accordion-Groups (from ui-bootstrap). With the master-tree version of sortable i could listen to ng-mouseover/ng-mouseleave inside the accordion headers but with the 1.2 version, the mouseevents are only listening as long as i haven't done any sorting. After performing any change to the sortorder, the mouseevents become deaf...
Here's a Plunker: http://plnkr.co/edit/n8yms9pb7uJp77zZ9LFK?p=preview
Can anybody give me some advice how to fix that?
Thank you
Identity Problem.
elementInsertedByDropping !== elementSelectedAndDragged
In the console, one can verify the assertion above. So that narrows down the category of problem to a relatively familiar one.
I'm learning Angular myself, and I'm also having trouble with ui-sortable; please don't regard my opinions as definitive. However, I believe that the problem is that the $watch listeners need to be re-bound to the new element, as it is being created asynchronously outside of Angular.
The "ng.$rootScope.Scope" documentation describes this situation somewhat clearly in the $apply section. If I am correct, you would need to either $scope.$apply(...) code in your controller, or [preferably] write a custom directive that handles the insertion.
Fortunately, it seems that jQuery-ui-sortable's "update" event can be easily used in a custom directive to ensure that the element is bound. I found that bloggers respectTheCode and Michal Ostruszka discuss the problem of writing jQuery-ui-sortable directives in fairly clear terms; so does a fellow named Greg Gigon and several others, but I'm only allowed to offer you two links at this point.
If I can provide more precise information at a later point, I will revise this answer; I'm still learning this stuff myself, and I would like to know how to do something quite similar.
[edit: I'm not familiar enough with Angular-UI-Sortable to know whether this is a bug or simply missing functionality.]
Looks like a bug in ui-sortable.
My guess: It seems to be losing the bindings from the event directives, probably because it's destroying the old DOM elements and creating a new ones without re-attaching the scope with $compile. I'd save this plunk and submit and issue on their GitHub repository
my premise was wrong. while AngularJS was certainly slowing things down, it was not due to the problem I describe below. however, it was flim's answer to my question - how to exclude an element from an Angular scope - that was able to prove this.
I'm building a site that generates graphs using d3+Raphael from AJAX-fetched data. this results in a LOT of SVG or VML elements in the DOM, depending on what type of chart the user chooses to render (pie has few, line and stacked bar have many, for example).
I'm running into a problem where entering text into text fields controlled by AngularJS brings Firefox to a crawl. I type a few characters, then wait 2-3 seconds for them to suddenly appear, then type a few more, etc. (Chrome seems to handle this a bit better.)
when there is no graph on the page (the user has not provided enough data for one to be generated), editing the contents of these text fields is fine. I assume AngularJS is having trouble when it tries to update the DOM and there's hundreds SVG or VML elements it has to look through.
the graph, however, contains nothing that AngularJS need worry itself with. (there are, however, UI elements both before and after the graph that it DOES need to pay attention to.)
I can think of two solutions:
put the graph's DIV outside the AngularJS controller, and use CSS to position it where it's actually wanted
tell AngularJS - somehow - to nevermind the graph's DIV; to skip it over when keeping the view and model in-sync
the second option seems preferable to me, since it keeps the document layout sane/semantic. is there any way to do this? (or some, even-better solution I have not thought of?)
Have you tried ng-non-bindable? http://docs.angularjs.org/api/ng.directive:ngNonBindable
<ANY ng-non-bindable>
...
</ANY>