While profiling Angularjs app I can see that on a slow page digest runs 5 times and many expressions evaluated too many times and many expensive controller functions are called multiple times as well.
Is there a way to prevent watched expression from being evaluated more than 1 time within a single digest?
Few examples to better illustrate the question:
I have an icon picker with 300+ icons, each icon has {background: colorPicker.color}
I have a very expensive function to check if page is modified and needs saving. It does a deep equals on initial version and current version of a very large object.
So I know that when digest is running I do not need to re-evaluate these.
I would like to have a way to cache calculated values within a single digest cycle.
One time binding is not an option, these watched expressions do change, just not within single digest.
Related
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.
It seems like angularjs 1.x filter is not recommended, with the reason given below:
http://blog.scalyr.com/2013/10/angularjs-1200ms-to-35ms/
Angular runs every single filter twice per $digest cycle once something has changed. This is some pretty heavy lifting. The first run is from the $$watchers detecting any changes, the second run is to see if there are further changes that need updated values.
I wonder if it make sense to either:
Use bind-once with filter
Write custom directive that ensures no additional watch is required?
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 am using angularJs v1.3.10, And I am making a large application having complex array, I am using ng-repeat on these datastructures.
Some part of pages also use timer, which changes the time per second so my data structure changes per second, so complete dom related to this array reloads. What should be the right process to implement timer in ng-repeat.
initially $apply cycles are around few hundred ms, but after few operations, it goes to few seconds.
what should i do, for page performance.(I am also adding classes dynamically inside ng-repeat).
I understand that both scope watchers and filters are executed repetitively within the digest loop. However the following is a bit unclear to me:
Are they executed the same amount of cycles?
Are both triggered by the same circumstances?
Watches are checked every digest cycle. And a $watch can watch an Angular expression. Inside an expression there can be a filter which Angular must evaluate (using $interpolate) to decide if the watched expression has changed.
Are both triggered by the same circumstances?
So, filters aren't run directly by $digest but are rather run as a consequence of a $watch. In effect, filters are triggered by the evaluation of watched expressions.
There's a few reasons filters get talked about specially with regard to the performance of $digest. Looking at these two expressions:
Expression 1: {{searchText+2}}
Expression 2: {{searchText | myFilter:true}}
Both expressions will be evaluated everytime a $digest is triggered. So one risk area is simply that myFilter could be complex and end up using a lot more processor cycles than something simple (like the above +2).
A bit less obviously, a filter can cause extra runs of all watchers if it's not idempotent. On each run of $digest a dirty bit is set if any of the watches results in a change. And it reruns all the watches again if that dirty bit is set. This allows any changes to be propagated. For instance if searchText is changed by one of the watches then Angular needs to give all the other watchers a chance to see if their results depend on searchText and thus should change.
Herein lies the difference between expressions 1 and 2 above. As a worst case scenario imagine myFilter returns a random number. $digest runs, sees a change when evaluating the watches (the previous result of the filter doesn't match the new result) so it runs through the watch list again. Because the filter returns a random number it's results have very likely changed, triggering yet another run of $digest. etc, etc... Angular has a built in stop after 10 loops through it's watch list- it figures if the results don't stabilize after 10 tries then something is wrong and so it gives up throwing "Error: 10 $digest() iterations reached. Aborting!"
Are they executed the same amount of cycles?
Therefore at a minimum each watched expression (including any filters) is run twice per $digest. Once because of the change that triggered $digest and once more to check if that result needs to propagate. If that propagation resulted in any changes then the watches and filters will be run again until nothing changes.
And, of course, if multiple watched expressions use the same filter then that filter will be run, following the above, for each expression.