Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Damon Gentry
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Damon Gentry

Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3

By
Published in Comments (14)

A while back, I looked at using the Scope.$evalAsync() method as a means to execute code asynchronously within an AngularJS context without using the $timeout() service. In AngularJS 1.3, they've added a new Scope method - Scope.$applyAsync(). After reading the documentation, the difference between new $applyAsync() method and the existing $evalAsync() method was not readily apparent. As such, I wanted to dig through the AngularJS source code to see how these two asynchronous execution methods differ.


 
 
 

 
Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3.  
 
 
 

Both $applyAsync() and $evalAsync() will update a global queue of pending expressions (each method deals with its own global queue).

Both $applyAsync() and $evalAsync() evaluate an expression asynchronously; however, only $evalAsync() can accept a collection of override "locals" to be used during the evaluation.

Both $applyAsync() and $evalAsync() will schedule a timeout as a backup to make sure that the expression gets executed in a timely manner; however, $evalAsync() will only initialize a timer if the control flow is not already inside a $digest. $applyAsync(), on the other hand, will always schedule a timeout; that said, $applyAsync() will cancel the timer if it can evaluate the expression before the timer is needed.

Both $applyAsync() and $evalAsync() execute the pending expression inside a try/catch block and then pipe errors to the $exceptionHandler() service.

So far, the two methods appear to do exactly the same thing. The difference doesn't become evident until you look at the actual $digest execution. When AngularJS executes a digest, it walks the Scope tree and executes $watch() bindings until no more dirty data is produced. During this lifecycle, both the $applyAsync() queue and the $evalAsync() queue get flushed; but, this happens in two very different places.

The $applyAsync() queue only gets flushed at the top of the $digest before AngularJS starts checking for dirty data. As such, the $applyAsync() queue will be flushed, at most, one time during a $digest and will only get flushed if the queue was already populated before the $digest started.

NOTE: Within the $digest, $applyAsync() will only flush if the current scope is the $rootScope. This means that if you call $digest on a child scope, it will not implicitly flush the $applyAsync() queue.

The $evalAsync() queue, on the other hand, is flushed at the top of the while-loop that implements the "dirty check" inside the $digest. This means that any expression added to the $evalAsync() queue during a digest will be executed at a later point within the same digest.

To make this difference more concrete, it means that asynchronous expressions added by $evalAsync() from within a $watch() binding will execute in the same digest. Asynchronous expressions added by $applyAsync() from within a $watch() binding will execute at a later point in time (~10ms).

Outside of a $digest, both the $applyAsync() method and the $evalAsync() method will populate their respective queues and then initialize a timer. When the timer executes, it triggers a $digest on the $rootScope.

Now, even with this level of understanding, it's still not entirely clear what the need for the new $applyAsync() method is. I suppose it's somewhat more likely to allow for DOM (Document Object Model) rendering before the expression is evaluated; but, I don't believe this is guaranteed (depending on when $watch() bindings were initialized).

To get more answers, I looked in the AngularJS source code to see if the $applyAsync() method was being used internally. And it was - within the $http service. It looks like the $httpProvider now allows you to flag the use of $applyAsync() so that AJAX (Asynchronous JavaScript and JSON) requests that complete within a ~10ms timeframe can be grouped into the same $digest (rather than triggering an individual $digest per AJAX response).

So, this looks like a micro-optimization that allows you to group sets of asynchronous expressions. I'll have to noodle on this a bit more; even after all this, I'm still not sure I would know when or why to replace $evalAsync() method calls with $applyAsync() method calls.

Reader Comments

8 Comments

I found it useful to rename $evalAsync. I call it $digestAsync

Now, if we compare $digestAsync vs. $applyAsync it is quite obvious what are the differences:

1. $applyAsync queues a request for $apply while $digestAsync queues a request for $digest

2. Executing $applyAsync inside apply phase will not cause additional apply while executing $digestAsync inside of a digest cycle will not cause additional digest cycle

Which one to use ?

I guess the answer is the same as asking "should I use $apply or $digest?"

1 Comments

Nice post, can you tell me how you get the nice Angular outputs in your console, is this some add-on or you did it yourself?

4 Comments

Another key service that uses $evalAsync is $q, I've been tracing through its functionality.

Can you tell me anything whether there is any difference between how the DOM gets repainted when using $apply / $applyAsync / $evalAsync?

I'm trying to help out on an indexedDB library and we're finding that making a series of async calls which invoke $rootScope.$apply is repainting the DOM on each call. I've found that since $q.defer().resolve(...) implicitly invokes $evalAsync and therefore $digest, I'm wondering if we should avoid directly calling $apply and just rely on letting $q take care of handling the digest for us... Any thoughts?

https://github.com/bramski/angular-indexedDB/issues/19

15,878 Comments

@Danny,

All good questions. All of the DOM manipulation, in AngularJS, is done through directives like ngIf, ngRepeat, ngShow, ngHide, etc. Each of these handles DOM manipulation for their particular use-case; so, there's not one-size-fits-all explanation. That said, the directives do seem to follow the general implementation pattern:

function link( scope, element ) {
. . . scope.$watch( someAttribute, function() { ... do DOM manipulation ... } );
}

Typically, the directive does DOM manipulation in response to a $watch() callback. This is why it is generally *safer* to query the DOM in $evalAsync() if that DOM is being manipulated by another directive -- it gives that other directive "time" to mutate things during its $watch() callbacks.

That said, AngularJS is also very tightly integrated with asynchronous things like $timeout() and $q(). If you are using those, you do NOT NEED to explicitly call $apply() since those services will automatically call it for you. As such, if you call it directly, you're only making the framework do more work.

Without more details, that's really all I can say.

15,878 Comments

@Marco,

The output is just from the "console" object in Firefox's Firebug plugin. But, Chrome devtools has very similar features:

console.log( "some data" )
console.info( "some data" );
console.error( "an errror" );
console.warn( "a warning" );
console.table( "an array of hashes" );

There's even more functions than that - I think the console object can do "timing" and what not, but I've never really used it.

15,878 Comments

@Ori,

I would probably just default to using $evalAsync() unless I had a reason to using $applyAsync(). Since I've written this, I don't think that I've actually come across a use case for it that really made a lot of sense in my head. But, I'll keep thinking.

4 Comments

@Ben,

Thanks for your reply. I was more keen on understanding rendering, rather than manipulation; as in, how repaints and re-renders get triggered after the $digest process and whether there is a difference in how things get rendered if using $apply versus $applyAsync versus $evalAsync. It sounded from the article like $applyAsync has about a 10ms threshold for subsequent async requests to get 'grouped' together in the same $digest: "requests that complete within a ~10ms timeframe can be grouped into the same $digest (rather than triggering an individual $digest per AJAX response)." So, I was curious whether you knew if $evalAsync worked similarly and whether this means that angular will only trigger 1 DOM re-render for these grouped updates. Sounds like $apply would trigger a re-render every time it is called.

I'm currently investigating some performance issues on a angular indexedDB service here: https://github.com/bramski/angular-indexedDB/issues/19

We've noticed that the UI freezes when we use an upsert method over a large data set (the example was upserting 1000+ items into an indexed DB). Mr. Spock would say that the UI freezing when making a database transaction is highly illogical. I'm not sure how familiar you are with indexedDB, but it was written to work through async transactions; so when upserting an item into the database, in angularland you would ideally use the $q deferred/promise approach and handle the results of the transaction in a .then().catch() chain. Or with the built in .onsuccess .onerror listeners that come stock in vanilla JS. The problem is that the original author of the library created an arbitrary wrapper around $q, then wrapped every single one of these database transaction methods with $rootScope.$apply and used his strange $q wrapper concoction to resolve the request. We believe the use of $apply is a dealbreaker because it's triggering a re-render for each of the upserts that get applied to the database. Not to mention pointless since $q handles the apply for us already.

(phew)

Anyway, that's the situation. After doing some digging, we found that since $q contains a call to $evalAsync internally, calling $apply explicitly is (exactly like you said) causing the framework to do MUCH more work. I guess one of the takeaways for people is to wield $apply, $applyAsync, and $evalAsync carefully, especially when using the $http or $q services, since both of those do that internally already. But that's broaching a completely different topic which I'm sure you've already covered :]

Thanks again!

(and sorry about the novel)

1 Comments

@Stepan,

no , only $applyAsync() execute $rootscope.$digest(). $digestAsync() ,though there is no such thing , runs digest cycle for its current scope and its children scopes , which is not similar to $rootscope.$digest().

1 Comments

I ran into interesting situation:

1. $evalAsync in primary component adds new item to the queue.
2. It is however cleared in the $digest run by another component on its scope (imagine both catch document.addEventListener('click', ...).
3. So the scope of the primary component is not updated (cf. if (asyncQueue.length) condition in $evalAsync).

So effectively another component stole $digest command to my component which remains unupdated.

1 Comments

1. executes evalasync of expression in current scope while current scope digest is in progress( scope.$digest() is in progress not $rootScope.digest() )
2. So applyasync queue will not be flushed
3. but at every digest loop evalasync queue is going to be flushed, that means there is no $rootScope.$digest() happens at this time.
4. will it contradict with the statement

"Outside of a $digest, both the $applyAsync() method and the $evalAsync() method will populate their respective queues and then initialize a timer. When the timer executes, it triggers a $digest on the $rootScope."

5. that means sometime evalasync triggers $rootScope.$digest() by triggering timeout and sometime not triggers $rootScope.$digest() (as evalasync is already executed in the digest of individual scope(scope$digest() ) while it is in progress)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel