Does AngularJS rendering of a partial block the download of other templates? - angularjs

So I'm running into an issue on Internet Explorer(works on all other browsers) where it seems like a directive's HTML is taking 20+ seconds to retrieve from the server. The page is set up like this:
<div class="groups-page">
...
<test-directive></test-directive>
<div class="groups">
<h1 ng-repeat="x in records">{{x}}</h1>
</div>
...
</div>
Records is array with about 5000 items. Looking at the networks tab, the groups-page HTML is loaded in several ms. Now since we have 5000 records, I'm assuming it takes a lot of time to render. As a result of this, I see that the test-directive HTML just sits there in the network tab and finally is "downloaded" after 20 seconds. Am I right in saying that I need to limit the amount of data bindings here and that's what is causing the test-directive HTML to take so long to download? If I set a limitTo on the ng-repeat, it makes it much faster the more I limit it.

The issue is most likely due to the sheer amount of work Angular has to do to generate and bind 5,000 copies of your template. What you will see in the browser is that your client downloads the data, and then as soon as it is loaded, it hangs for a while while it generates the necessary HTML to add to the DOM. Once it finishes, the page becomes responsive again and the elements are drawn on the screen. At very large amounts of data, your browser starts to slow down with the number of DOM elements it is keeping track of, even after it is painted.
You have several options to mitigate this, including the following:
Reduce the number of data-bindings in your template
Page your data so that you aren't showing so many at a time
Use "infinite scrolling" (performance gets worse as items are added to the DOM while you scroll)
Look into implementing virtual scrolling for your list (essentially having the DOM only render a portion of the actual results that shifts as you scroll).
I ran into the same issue myself, and ultimately went with option 4. It is probably the most work out of options above, but the solution is handling an array of 200,000 items quite smoothly. If you don't want to create your own virtual scroll, there are a handful of open source implementations out there now that are worth looking into.

Related

Delay generating directive until after page is ready and responsive

I'm working on a single-page app where some parts are really slow. They're slow because I'm displaying 400 complex things in a repeater for the user to scroll through. Each thing is generated by a fairly complex directive that does a ton of data binding and expression evaluation, adds one or two click handlers, and displays a couple of images. In some cases, I also need a grayscale CSS filter on those images, but that really seems way too slow.
I have of course already turned most of my data binding into one-time data binding, but simply generating the 400 things for the first time is still slow. It's initially hidden through ng-if, which speeds it up when I'm not showing it, but once I do need to show it, everything waits 10 seconds for that to happen. I would like to load it in advance, but using ng-show instead of ng-if means the loading of the entire app has to wait for this.
What I would like, is to load the rest of the app, and then, while we wait for user input, start creating these 400 things so they're ready once I need to show them. I don't want the user to notice how slow this is.
Problem is, I have no idea how to do this. Any ideas?
Edit: Having thought about this (and discussed this with my wife), I'm seeing two options (that I conceptually understand, at least):
The easy, quick, boring and cowardly solution is to simply not show the 400 things at the same time, but cut them in pieces and show 40 at a time. That should make it a lot quicker, but also less nice, as the user needs to click around to access all the data.
The interesting solution is to write a new ng-repeat that generates the 400 transcluded copies of the template each in their own asynchronous event, so they don't block user interaction. I really like this idea, but there's one big downside: it's an ambitious idea with deep Angular magic, and I don't have much time available.
OK, not seeing your code structure, through Q&A, I'm trying to get clarification. If I understand you correctly, I believe the solution is to process your images asynchronously and remove reliance of creating/processing them on the fly when the view is visible (i.e. via clicking on a button/tab to 'show' the array 'view' and thus triggering the ng-repeat). BTW, this solution assumes the delays are because the images are being processed rather than because they are being shown.
METHOD 1 (less preferred)
To do this, it's best to create an 'ImageDataService' service, where it get's kicked off at application start, and proceeds with building this array and taking whatever time it needs asynchronously without caring what view is showing or not. This service will be injected into the proper view or directive controller--perhaps where the current ng-repeat scope is.
Meanwhile, you also need to change the directives inside your ng-repeat to get the pre-built data from the array provided by ImageDataService rather than actually doing the calculation at view time. If the data for that directive is not ready, the directive will show something like 'loading...' otherwise, the directive will simply show the prebuilt images. This way, it doesn't matter when ng-repeat is triggered (i.e. its view is showing), because it will just show what are processed and ready at that point and the display the rest as 'loading...'.
METHOD 2 (I prefer this)
Alternatively, and maybe better, you can forego creating a pre-processed array in a service as in METHOD 1 and instead modify your directives to process their data asynchronously by invoking a service method that returns an asynchronous promise like this:
(code inside a controller)
var promise = ImageDataService.processImage(directiveData);
promise.then(function() {...set the directive image attributes..})
Needless to say, the ImageDataService.processImage() method needs to return a promise when it is done processing the data. The directive, as usual, will show 'loading...' until then. Also, although ImageDataService no longer needs to pre-populate its array mentioned in METHOD 1, it might be a good idea to save the processed images to a similar array anyway to serve as cache and not reprocess them needlessly. NOTE, in this case you don't need to have processImage() inside a service--but it is really good 'separation of concerns' practice to reserve the work of asynchronous data processing (ajax, etc) within a service which can be injected app-wide rather than within a controller.
Either of these 2 general tacks should work.

AngularJS input slow to fill

I have an AngularJS app with a ng-repeat. This ng-repeat displays only about 60 watchers (the list of ng-repeat contains quite big JSON objects though). In the same page I have an input. When I try to write inside this input, it is very slow (displays one letter every second).
I tried to optimize as much as possible but the inputs in my whole app are slow. If I disable the ng-repeat, the inputs goes back to normal speed.
Can someone explain to me why inputs are slow? And if I can solve this problem?

Overcoming UI blocks when updating ng-repeat data

So yes, apparently it is possible to have long grid with lots of rows built with angular. But then a problem comes with data updates.
You see if I just get all (let's say 10 000 rows) and render them in my grid - that works. It just takes a few seconds initially. And
a) I don't have all the date up front
b) I need the grid to be responsive immediately.
I can just do that with throwing let's say only 100 rows at the beginning, and then slowly update data as it becomes available. And that turns out to be the problem. Everytime you push new rows into $scope.data - it blocks UI. So I need to be smart about these updates.
Maybe I should set an interval and update the data only every few seconds? That seems to be not working
Maybe I should somehow watch for mouse movements and once it stops moving - start/resume adding rows, once mouse-movement detected seize adding rows and wait for another chance? - What if user never stops moving the mouse? (say some sort of a psycho)
Experimenting with _.throtle and _.debounce didn't get me anywhere.
You guys have any ideas?
UPD: here's a crazy one: what if? instead of waiting till angular updates the DOM, create entire DOM structure in memory, right before the digest cycle (with no data) and then insert that HTML chunk (that would be faster, right?) And after that let angular do its magic, data should appear. Would that work?
You are going to run into performance issues when something changes even if you can get all those rows rendered to the DOM. And your user probably isn't going to scroll through 10000 rows anyway. I would just use pagination. e.g.:
<div ng-repeat="item in items | startFrom:currentPage*itemsPerPage | limitTo:itemsPerPage"></div>
If you really want to have everything on one page you could load the rows as the user scrolls. If you are interested in that solution checkout http://binarymuse.github.io/ngInfiniteScroll/
One of the things I've noticed that I stupidly used to ignore - the container has to have fixed height. That significantly makes updates faster. Although technically it doesn't solve the problem entirely

how can I exclude an element from an Angular scope?

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>

How to speed up an AngularJS Application?

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

Resources