42

We are pounding our heads against performance issues with an Angular app we are building for a bank.

Unfortunately, it is a breach of contract to show snippets of the code. Regardless, I can describe some of the main issues going on, and I am hoping that best practice can be recommended.

Applications Structure:

  • Essentially, a giant multi-form page.
  • Each form is its own partial, with nested controllers and partials about 3 levels deep.
  • The same forms are ng-repeated over a collection of json objects.
  • Each form is bound to the object / model that it is repeated over.
  • We are supposed to support anywhere from 1-200 forms on the page.

If you take a look at the timeline. We are spending a great deal of time in the jQuery parse html method, jQuery recalculate stye method, the GC Event (Garbage Collection). I imagine minimizing these should speed things up a bit. They are all a part of the Angular lifecycle, but there may be better ways to avoid them. Here are some screenshots of the profiler:

Recalculate Style GC Event

Ultimately, the app is sluggish as the number of repeated forms goes above 5. Each form is relatively unrelated to the others. We have tried not to watch any shared properties between the forms.

Community
  • 1
  • 1
trevorewen
  • 469
  • 1
  • 5
  • 5
  • 2
    And the question is...? – Stewie Jul 15 '13 at 14:21
  • Have you tried tracing back this function call to the code your own code-base (or angular that calls it?) @Stewie I believe the question (although somewhat codified) is what are the general performance bottle-necks that are likely to arise in an AngularJS app? How should performance/optimization be approached? I believe the Batarang plugin for chrome has something for perfomance (just checked, sure does have a performance tab though I haven't used it, it may be helpful). – shaunhusain Jul 15 '13 at 14:26
  • 1
    "what are the generally performance bottle-necks are likely to arise in an AngularJS app?" the same bottlenecks as any javascript app, the more you create objects , the longer the GC will be. There are good resources on HTML5rocks website about this. – mpm Jul 15 '13 at 14:31
  • @mpm I would imagine you can also get into issues though with creating events that are bubbling up and down by using $emit/$broadcast in unchecked ways. I mean yes what you're saying is generally still the problem lots of objects being created/functions being called, same general problem, but with no programmer controlled event bus you wouldn't normally encounter this in JS outside of Angular. – shaunhusain Jul 15 '13 at 14:46
  • 1
    Could you mock up a jsfiddle or plnkr? I understand you can't share you clients code but it shouldn't be too hard to mimic the structure so we can get a better idea of how you are implementing things. – Brian Lewis Jul 15 '13 at 16:14
  • $broadcast was a definite target, and we moved to using more objects with $watch statements for notification. As far as putting together a js fiddle, without going overboard, it would be hard. I know that seems like a copout, but we are talking about a complex page with a wide variety of directives, ng-includes, and styling to account for. – trevorewen Jul 15 '13 at 16:58

9 Answers9

21

You need to create custom directives in order to curb the performance issues with angular. Unlike ember angular comes with all the bells and whistles turned on and it's up to you to tone it down. Here are a few directives I've created to help you out. Not all data in your app needs to be two way data bound and as a result you can save valuable cpu power by forgoing watch expressions in the page where needed. All of these directives bind data one time and leave it alone.

https://gist.github.com/btm1/6802599

https://gist.github.com/btm1/6802312

https://gist.github.com/btm1/6746150

One of the answers above talks about ng-repeat having huge performance hits so I give you "set-repeat" a one time data binding repeat directive :)

btm1
  • 3,866
  • 2
  • 23
  • 26
8

It is hard to provide a solution without more information about your problem, but I recently experienced (and solved) a performance issue that may be similar to what you saw, and was unrelated to the $digest cycle.

Most discussion of angularjs performance you will find (including the excellent post from Misko) is about the performance of dirty checking and the $digest cycle. But that is not the only performance issue you can experience with angularjs. The first step should be to determine if the digest cycle is your problem or not. For this, you can use batarang, or just look at your app and at when precisely it is sluggish. When the digest cycle is slow, essentially any interaction with the UI will be slow.

OTOH, you can have an app with a fast digest cycle, that is slow only when loading, switching views, or otherwise changing the sets of components to display, and this can manifest in profiling as a lot of time spent in parsing HTML and garbage collecting. In my case this was solved by doing some pre-computation of the html template to display, instead of relying on ng-repeat, ng-switch, ng-if everywhere.

I was using an ng-repeat="widget in widgets" containing an ng-switch on the type of widget, to display an arbitrary set of widgets (custom self-contained directives). Replacing this with code to generate the angular template for the specific set of widgets sped up route switching from ~10s to practically instant.

You can see the google groups thread above for a little more info on how I solved my particular problem, or provide more information about your application if you want some specific suggestions.

Community
  • 1
  • 1
jssebastian
  • 1,189
  • 1
  • 11
  • 12
  • Were you still getting the 2 way data-binding with this approach? – Matheus Mar 01 '15 at 17:09
  • @matheus: yes, the contents of each widget still had two way binding. On the other hand, the list of widgets to display did not, it was generated by concatenating each individual widget's template. – jssebastian Apr 05 '16 at 17:46
6

To improve performance in production read very nice one-liner below:

Quoting AngularJS Documentation:

By default AngularJS attaches information about binding and scopes to DOM nodes, and adds CSS classes to data-bound elements:

As a result of ngBind, ngBindHtml or {{...}} interpolations, binding data and CSS class ng-binding are attached to the corresponding element.

Where the compiler has created a new scope, the scope and either ng-scope or ng-isolated-scope CSS class are attached to the corresponding element. These scope references can then be accessed via element.scope() and element.isolateScope().

Tools like Protractor and Batarang need this information to run, but you can disable this in production for a significant performance boost with:

myApp.config(['$compileProvider', function ($compileProvider) {
  $compileProvider.debugInfoEnabled(false);
}]);

You can read more details here

Abbasi
  • 617
  • 7
  • 14
3

Generally, AngularJS will perform poorly if there are more than 2000 data-bindings active, i.e. 2000 items in the scope that are being dirty-checked each $digest-cycle. Ng-repeat has a big performance impact because of this; each repeated items sets up at least two bindings, not counting any additional data or directives that are used inside the item.

One of the developers behind AngularJS gives an excellent description of the details of dirty-checking, and its performance in this SO answer:

https://stackoverflow.com/a/9693933/179024

The comment thread below that answer is worth a read, and I also share some thoughts about it in an answer further down on the same page:

https://stackoverflow.com/a/18381836/179024

Community
  • 1
  • 1
MW.
  • 12,550
  • 9
  • 36
  • 65
  • I think Misko's answer, while great info, only partially addresses this question. A big part of the issue here appears to be that Angular is creating each element individually, rather than batching them into the same fragment. – DNS Sep 13 '13 at 17:50
  • The issue could simply be that with a number of repeated forms, each of which in istelf has a number of repeated items, the number of items being watched each time $digest runs causes the site to slow down. A good way to check this would be the batarang plugin for Chrome, which will show digest time in real-time. But it is kind of hard to pinpoint the issue as the OP can't show any code. – MW. Sep 16 '13 at 06:59
  • 2
    The OP does show the event timeline, and most of the shown activity is a long series of HTML parses. There may be a good reason why Angular is doing that versus creating them all in one fragment, but I think that's why it's slow here; the dirty-checking doesn't seem to be a big contributor. – DNS Sep 17 '13 at 03:59
  • I doubt that the html parsing above is happening inside the digest cycle, so this likely has nothing to do with $digest performance. – jssebastian Sep 26 '13 at 23:07
  • @jssebastian - No, this could indeed be unrelated to the $digest time. As noted, if the OP want to investigate it, I would recommend the Batarang-plugin. – MW. Sep 27 '13 at 06:52
2

Am sorry for putting it as an 'answer' because i do not have enough points yet to make a comment.

We have run into similar issues with our AngularJS app. Using 'batarang' it seems having to deal with a large number of scope objects and their relevant $watch expressions creates a performance hiccup. This has set us wondering if another framework or something like ReactJS should be used instead to take care of the 'view' part.

5122014009
  • 3,766
  • 6
  • 24
  • 34
2

try avoiding the following

  1. please avoid using ng-repeat if you have more than 50 elements in the list at a time and avoid manual watches
  2. do not use ng-click, ng-mouseenter,ng-mouseleave etc mouse events blindly till it is a dire need , try to reduce their numbers by using $event object along with event propagation concepts of js

  3. where ever possible use scope.$digest instead of scope.$watch, this ensures that digest cycle is executed only on the child scopes

    1. try having nested scopes i.e. one or two controllers inside one parent controller and keep the reusable logic in parent , i used this in nested states while using Ui-router (to fulfill a req where change of URL was required without page refresh ).

    most important! REMOVE ALL FILTERS FROM HTML!

all the above trigger a digest cycle on all the scopes of your application so there is a high probability that even when the view has been rendered angular is again executing relentless digest loops

Rishul Matta
  • 3,383
  • 5
  • 23
  • 29
  • 2
    What would you use instead of ng-click and ng-repeat? – Bradley Weston Apr 10 '14 at 19:50
  • use javascript concepts of event capturing and bubbling to reduce ng-click and for ng-repeat use single binding property (newer versions of angular js) – Rishul Matta Jul 29 '14 at 03:22
  • I wouldn't go against the grain unless you are having performance problems or know you will. Ex. If you don't use the built-in directives for events, if you want to update your models, you're stuck triggering .$digest() (which triggers dirty checking from the current scope downward) or worse $apply() (which triggers this from $rootScope), which is less efficient than using ng-click or others (which just registers another watcher for that scope). – TaylorMac Oct 05 '14 at 21:02
  • 1
    Removing all filters from HTML defeats the purpose of using filters, you might as well use a service instead. If you keep simple operations in filters (what they're intended for) you won't incur any noticeable performance hits. – TaylorMac Oct 05 '14 at 21:08
1

A middle ground between moving the DOM manipulation into custom directives and the $watch problems with lots of $watches is to use "bind-once" semantics.

This is great for data that is immutable once the data is made available. See bindonce

user239558
  • 6,964
  • 1
  • 28
  • 35
0

This will only be a link! This is just an idea I had when I was reading this, I haven't explored this yet but someone probabbly did so I'm waiting for their reply on my idea. How about using shared web workers to get lots of heavy processing out of the ui thread? https://github.com/h2non/sharedworkers-angular-poc

The other idea I had was a simpler one. Would your app benefit from infinite scrolling? I mean these forms probabbly dont all fit on the screen, they are not connected to eachother so why not draw them as they are needed? Load them up in memory then draw them accordingly.

Zoneh
  • 192
  • 5
  • 19
0

Just like in any other performance optimization, it is important to know how to profile the application to find the true bottleneck. Then you can solve them one by one. I usually fight the bottlenecks in the following order:

  • my javascript code
  • angular expressions (complex watchers and filters) that run on each idle digest cycle
  • angular constructs (ng-repeat, copying objects for digest cycle)

I have profiled an angular example step by step showing how to identify a bottleneck at each step. http://bahmutov.calepin.co/improving-angular-web-app-performance-example.html

gleb bahmutov
  • 1,801
  • 15
  • 10