27

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)?

New Alexandria
  • 6,951
  • 4
  • 57
  • 77
woelling
  • 325
  • 1
  • 3
  • 8
  • I worked on this question here to help people speed up NG2 apps, it should be able to provide you with some insights: https://stackoverflow.com/questions/42583421/how-to-compress-and-optimise-an-angular2-application – HappyCoder Jul 06 '17 at 11:35
  • How did you measure this? – WJA Feb 01 '19 at 21:23

11 Answers11

28

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.

Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
21

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/

Brad Green
  • 1,296
  • 8
  • 5
  • What an awesome tool! Thanks, thanks, thanks! This should be installed on every developer computer ;-) – Sebastian Jan 04 '14 at 12:56
  • Nice tool, but still it shows me that ngModelWatch is taking 91.9% of the total time, so really pinpointing is hard this way, maybe I'm using it in the wrong way – Highmastdon Oct 14 '14 at 09:19
19

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?

Community
  • 1
  • 1
hcpl
  • 17,382
  • 7
  • 72
  • 73
13

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.

Jesus Rodriguez
  • 11,918
  • 9
  • 64
  • 88
7

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

yonia
  • 1,681
  • 1
  • 14
  • 12
1

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

doron aviguy
  • 2,554
  • 2
  • 22
  • 18
0

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

TGH
  • 38,769
  • 12
  • 102
  • 135
0

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.

Ceolter
  • 91
  • 3
0

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)

Soferio
  • 483
  • 6
  • 14
0

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><a href="https://shailendrapathakbits.wordpress.com/" title="code_lab_by_shail ">code_lab_by_shail</a> 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>

  <a href="https://github.com/shailendra9/objectobserver/blob/master/index.html" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>

</div>

<script src="../js/lib/ga.js"></script>

</body>
</html>
shailendra pathak
  • 622
  • 1
  • 8
  • 25
0

You can also improve performance in general by Disabling Debug Data

michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63