The following must be the most common error Angular.js developers have to troubleshoot at some point or another.
Error: $apply already in progress
Sometimes, a $scope.$apply call wasn't required after all, and removing it fixes the issue. Some other times, it might be needed, and so developers resort to the following pattern.
if(!$scope.$$phase) {
$scope.$apply();
}
My question is, why doesn't Angular merely check for $scope.$$phase and return without doing anything when a $digest loop is in invoked, or when $scope.$apply is called, rather than complain and throw an error so insidiously hard to track down.
Couldn't Angular just check for $$phase itself at the entry points of any methods which trigger a $digest and save the consumers from having to go through the trouble?
Are there any implications in leaving misplaced $scope.$apply calls around, other than unwanted watches firing, which the check for $$phase would prevent?
Why isn't a$$phase check the default, rather than an oddity?
Background
I'm upgrading Angular from 1.2.1 to 1.2.6 and that exception is being thrown when the page loads, so I have no idea where it's originating from. Hence the question, why is it that we even have to track down this obscure sort of bug with no idea of where in our code to look for it?
If you ever get this error, then you're using $scope.$apply() in the wrong place. I think the reasoning behind this error is as follows: if you're calling $apply in the wrong place, you probably don't fully understand the reason it exists, and you may have missed places where it actually should be called.
If such lapses are allowed to go by silently (by always testing $scope.$$phase first, for example), it would encourage sloppy usage, and people might get holes in their "$apply coverage". That kind of bug (where a $scope change is not reflected in the view) is a lot harder to track down than this error message, which you should be able to debug by examining the stack-trace (see below).
Why does $apply exist?
The purpose of $apply is to enable automated two-way data binding between $scope and view, one of the main features of Angular. This feature requires that all code that may potentially contain a $scope modification (so, basically every piece of user-code) is run below an $apply call in the call-stack. Doing this properly is actually not that complicated, but I think it's not documented very well.
The main 'problem' is that Javascript can have any number of active call-stacks, and Angular is not automatically notified about new ones. A new call-stack is created whenever an asynchronous callback is triggered, e.g., from a click, a timeout, a file-access, a network response, and so on. It's very common to want to modify the $scope inside such a callback function. If the corresponding event was provided by Angular itself, everything will just work. But there are times when you'll want to subscribe to 'outside events'. Google Maps events, for example:
function Controller($scope) {
$scope.coordinates = [];
//...
var map = new google.maps.Map(mapElement, mapOptions);
google.maps.event.addDomListener(map, 'dblclick', function (mouseEvent) {
$scope.coordinates.push(mouseEvent.latLng);
});
}
http://jsfiddle.net/mhelvens/XLPY9/1/
A double-click on the map of this example will not trigger an update to the view because the coordinates are added to $scope.coordinates from an '$applyless' call-stack. In other words, Angular does not know about Google Maps events.
How and where to use $apply?
We can inform Angular about the event by using $scope.$apply():
function Controller($scope) {
//...
google.maps.event.addDomListener(map, 'dblclick', function (mouseEvent) {
$scope.$apply(function () {
$scope.coordinates.push(mouseEvent.latLng);
});
});
}
http://jsfiddle.net/mhelvens/XLPY9/2/
The rule is to do this first thing inside every callback function for an outside event. The "$apply already in progress" error is an indication that you're not following this rule. If you have to handle Google Maps events often, it makes sense to wrap this boilerplate code in a service:
app.factory('onGoogleMapsEvent', function ($rootScope) {
return function (element, event, callback) {
google.maps.event.addDomListener(element, event, function (e) {
$rootScope.$apply(function () { callback(e); });
});
};
});
function Controller($scope, onGoogleMapsEvent) {
//...
onGoogleMapsEvent(map, 'dblclick', function (mouseEvent) {
$scope.coordinates.push(mouseEvent.latLng);
});
}
http://jsfiddle.net/mhelvens/XLPY9/3/
The onGoogleMapsEvent events are now Angular-aware, or 'inside events' if you will (I'm totally making these terms up, by the way). This makes your code more readable and allows you to forget about $apply in your day-to-day programming; just call the wrapper instead of the original event subscriber.
For a number of events, Angular has already done this for us. With the $timeout service, for example.
Debugging the "$apply already in progress" error
So let's say I use the wrapper, but, absentminded as I am, I still call $apply manually:
onGoogleMapsEvent(map, 'dblclick', function (mouseEvent) {
$scope.$apply(function () { // Error: $apply already in progress
$scope.coordinates.push(mouseEvent.latLng);
});
});
http://jsfiddle.net/mhelvens/XLPY9/4/
This $apply call does not follow our placement rule (onGoogleMapsEvent events are not outside events, after all; we granted them 'insideness'). If you double-click on the map, you'll see the error appear in the logs, together with a stack-trace:
Error: [$rootScope:inprog] $apply already in progress
...
at Scope.$apply (.../angular.js:11675:11)
at http://fiddle.jshell.net/mhelvens/XLPY9/4/show/:50:16
...
at Scope.$apply (.../angular.js:11676:23)
at dl.ondblclick (http://fiddle.jshell.net/mhelvens/XLPY9/4/show/:33:24)
...
I've left only the relevant lines: the ones that refer to Scope.$apply. When you get this error message, you should always find two Scope.$apply calls on the stack (or Scope.$digest calls, which serve a similar function; $digest is called by $apply).
The topmost call indicates where we got the error. The other one indicates why. Go ahead and run the Fiddle with your Javascript console opened up. In this case we can conclude: "oh, this was an 'inside event' subscriber, I can just remove my manual $apply call". But in other situations you may find out that the call lower down on the stack is also not positioned properly.
And that's precisely why I believe the error is useful. Ideally you'd only get an error when you neglect to call $apply altogether. But if Angular could do that, there would be no need to call the function manually in the first place.
Related
I am trying to implement a function to check for user inactivity. My online search has suggested that I can use the $rootScope.$watch to monitor activity. However, I am a bit concerned about the $digest trigger... as I can't seem to be able to find exactly when or which events trigger this. Any pointers will be appreciated.
Angular's $digest alone isn't going to solve the requirement, in my opinion. What if the user moves their mouse, shouldn't that restart the idle timer?
What I recommend is making a directive that creates an event listener on the $window object for both keydown and mousemove. Then create a service that will allow other components to receive notification when the user's idle state changes.
Something like this:
angular
.module('myApp', [])
.service('Idle', function(){
var self = {
callbacks: {
onidle: []
},
on: function(event, callback){
event = 'on' + event.toLowerCase();
if(self.callbacks[event] && typeof callback === 'function'){
self.callbacks[event].push(callback);
}
return this;
},
trigger: function(event){
event = 'on' + event.toLowerCase();
if(self.callbacks[event])
{
lodash.each(self.callbacks[event], function(callback){
callback.call();
});
}
return this;
}
}
return {
on: self.on,
trigger: self.trigger
}
})
.directive('idleNotification',
function(
$window,
Idle
){
return {
link: function($scope){
var timer = setTimer();
angular
.element($window)
.on('keydown mousemove', function(){
clearTimeout(timer);
timer = setTimer();
});
function setTimer(){
return setTimeout(function(){
Idle.trigger('idle');
}, 30);
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
Then use the Idle service like so:
Idle.on('idle', function(){
// do something here when the user becomes idle
});
Angular triggers $digest (generally) all by itself. Your $watch should "just work".
Oh - if you really need to initiate the $digest loop yourself, you should use $scope.apply() instead of calling digest directly.
What really happens behind the scenes though is all kinds of things actually trigger a $digest, including ng-click events, timeouts, watchers, long-polling, websockets, http calls, promises - anything that changes the internal application state that angular manages (via "dirty checking"). For more info, see here: https://www.sitepoint.com/understanding-angulars-apply-digest/ and https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest
To understand the Angular context, we need to look at exactly what
goes on inside of it. There are two major components of the $digest
loop:
When Angular thinks that the value could change, it will call $digest() on the value to check whether the value is “dirty.” Hence, when the Angular runtime is running, it will look for potential changes on the value.
$watch lists are resolved in the $digest loop through a process called dirty checking, so after the scope expression is evaluated and the $digest loop runs, the $scope’s watch expressions will run dirty checking.
During the $digest cycle, which executes on the $rootScope, all of the children scopes will perform dirty digest checking. All of the watching expressions are checked for any changes, and the scope calls the listener callback when they are changed.
For example
When we call the $http method, it won’t actually execute until the next $digest loop runs. Although most of the time we’ll be calling $http inside of an $apply block, we can also execute the method outside of the Angular digest loop.
To execute an $http function outside of the $digest loop, we need to wrap it in an $apply block.
That will force the digest loop to run, and our promises will resolve as we expect them to.
$scope.$apply(function() {
$http({
method: 'GET',
url: '/api/dataFile.json'
});
});
Hope this much understanding help!
I was trying to understand the in and out of angularjs.
I come across $digest loop, $watch lists etc, however my doubt is when an event fires at browser (Button click) how do angular knows it? In javascript if we subscribed to an event then, we get notification when the event fires. How do angular get those notification to invoke the $digest loop.(Please note that I am not trying to understand about ng-click directive.)
I tried google it, but I did not get the details. Any URLs or book name is just fine.
Thank you.
From Angular docs about Scope Life Cycle:
When the browser calls into JavaScript the code executes outside the
Angular execution context, which means that Angular is unaware of
model modifications. To properly process model modifications the
execution has to enter the Angular execution context using the $apply
method. Only model modifications which execute inside the $apply
method will be properly accounted for by Angular. For example if a
directive listens on DOM events, such as ng-click it must evaluate the
expression inside the $apply method.
To make sure Angular evaluates the $scope modifications, you need to use the built-in in handlers: ng-click, $timeout, etc. After executing the handler, Angular will make sure to perform a dirty check and update the DOM if necessary. The $scope.$apply() is called automatically.
In other cases, when using handlers not related with Angular: setTimeout(), document.body.addEventListener(), etc. the developer should take care by himself about dirty check, by calling manually $scope.$apply() in the handler.
For example:
1) Automatic dirty check by Angular (the recommended and correct way)
angular.module('App').controller('Ctrl', function($timeout) {
$scope.myText = 'Initial text';
$timeout(function() {
$scope.myText = 'Initial text after 1 second';
//$scope.$apply() is called automatically
}, 1000);
});
2) Manually evaluate the scope changes
angular.module('App').controller('Ctrl', function($timeout) {
$scope.myText = 'Initial text';
setTimeout(function() {
$scope.myText = 'Initial text after 1 second';
$scope.$apply(); // should be called manually
}, 1000);
});
The magic here generally happens in the ng-click directive (or one if it's related directives like ng-mouseenter, ng-mouseleave, ng-keypress, etc.)
Looking at the source, the directive simply registers a callback for the appropriate event using angular's JQLite (i.e. element.on(event, callback)). The callback itself executes the expression in the correct scope via $scope.$apply (which starts the digest loop).
If you are going to register your own event listeners outside of the normal angular directives, you'll want to follow the same pattern they use.
The source code is remarkably simple to read and is worth having a look at.
I'm using angular, and in an angularUI modal window I want to show the Drop In form from Braintree to get a payment method. Thus, I create the usual form (partial.html):
<form id="creditCard" >
<div id="dropin"></div>
<button type="submit" id="btnPay" >Pay</button>
</form>
and then I show the modal with this:
var modalInstance = $modal.open({
templateUrl: 'partial.html',
controller: 'ModalController'
});
Where ModalController contains the call to the Braintree setup:
braintree.setup($scope.clientToken, 'dropin', {
container: 'dropin',
onPaymentMethodReceived: function (result) {
$scope.$apply(function() {
$scope.success = true;
// Do something else with result
});
}
});
This will show the Drop In form from braintree nicely (the setup generates the form) and accept the credit card and expiration date, all working fine so far.
The problem is, each time I call the modal, the ModalController is executed, and thus the braintree.setup() is also executed. Then, when I enter the credit card number and the expiration date and hit pay, the onPaymentMethodReceived() event is triggered once per setup execution! That is, if the first time I call the modal it will trigger the event once, the second time it will trigger it twice, and so on. Like if each time I call setup, a new hook to the event is created.
Any idea on how to avoid this? Is there a way to "unbind" the onPaymentMethodReceived() event handler? I do need to call the setup several times since each time I call the modal, the clientToken may have changed.
Thanks for any help or pointer to help.
Calling braintree.setup multiple times in angular seems unavoidable, either for the asker's reasons, or simply because setup is called in a controller that may be instantiated multiple times in a browsing session – like a cart or checkout controller.
You can do something like this:
$rootScope.success = false;
braintree.setup($scope.clientToken, 'dropin', {
container: 'dropin',
onPaymentMethodReceived: function (result) {
if(!$rootScope.success) {
$scope.$apply(function() {
$rootScope.success = true;
// Do something else with result
});
}
}
});
I found I wasn't able to avoid having the callback fire multiple times (the number of times seems to explode each time I revisit the view - yikes), but I could test whether I had performed my actions in response to the callback. Since the $scope will be destroyed if I leave the view, $scope.success is effectively reset when I need it to be. Because each new controller will have its own $scope, setting a success flag on the $scope may only halt additional executions on that $scope (which seems to still be available to the callback, even if the controller has been "destroyed"), so I found that using $rootScope meant only one execution total, even if I re-instantiated the controller multiple times. Setting $rootScope.success = false in the controller means that once the controller is loaded, the callback will succeed anew – once.
I think it's handled by the API since then with teardown:
In certain scenarios you may need to remove your braintree.js integration. This is common in single page applications, modal flows, and other situations where state management is a key factor. [...]
Invoking teardown will clean up any DOM nodes, event handlers, popups and/or iframes that have been created by the integration.
https://developers.braintreepayments.com/guides/client-sdk/javascript/v2#teardown
(I haven't tried it yet)
The link given by Arpad Tamas does not contain the info anymore. So I am posting the info given by BrainTree for posterity ;) Especially since it took me a few tries to find it with a Google search.
In certain scenarios you may need to remove your Braintree.js integration. This is common in single page applications, modal flows, and other situations where state management is a key factor. When calling braintree.setup, you can attach a callback to onReady which will provide an object containing a teardown method.
Invoking teardown will clean up any DOM nodes, event handlers, popups and/or iframes that have been created by the integration. Additionally, teardown accepts a callback which you can use to know when it is safe to proceed.
var checkout;
braintree.setup('CLIENT_TOKEN_FROM_SERVER', 'dropin', {
onReady: function (integration) {
checkout = integration;
}
});
// When you are ready to tear down your integration
checkout.teardown(function () {
checkout = null;
// braintree.setup can safely be run again!
});
You can only invoke teardown once per .setup call. If you happen to call this method while another teardown is in progress, you'll receive an error stating Cannot call teardown while in progress. Once completed, subsequent calls to teardown will throw an error with this message: Cannot teardown integration more than once.
I've wrapped this code in a function that I call each time the related checkout ionic view is entered.
$scope.$on('$ionicView.enter', function() {
ctrl.setBraintree(CLIENT_TOKEN_FROM_SERVER);
});
var checkout;
ctrl.setBrainTree = function (token) {
braintree.setup(token, "dropin", {
container: "dropin-container",
onReady: function (integration) {
checkout = integration;
$scope.$emit('BTReady');
},
onPaymentMethodReceived: function(result) {
...
},
onError: function(type) {
...
}
});
// Prevents a call to checkout when entering the view for the first time (not initialized yet).
if (checkout) {
// When you are ready to tear down your integration
checkout.teardown(function () {
checkout = null; // braintree.setup can safely be run again!
});
}
};
There is this article I saw long time ago:
https://coderwall.com/p/ngisma
It describes a method that triggers $apply if we are not in an apply or digest phase.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Angular has the $scope.$evalAsync method (taken from 1.2.14):
$evalAsync: function(expr) {
// if we are outside of an $digest loop and this is the first time we are scheduling async
// task also schedule async auto-flush
if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) {
$browser.defer(function() {
if ($rootScope.$$asyncQueue.length) {
$rootScope.$digest();
}
});
}
this.$$asyncQueue.push({scope: this, expression: expr});
}
Which calls digest if we are not in a phase and adds the current invocation to the asyncQueue.
There is also the $apply, $digest and $timeout methods.
It is confusing.
What is the difference between all ways mentioned to trigger a digest cycle (a dirty check and data binding)?
What is the use case for each method?
Is safeApply() still safe? :)
What alternative we have instead of that safeApply() (in case we call $apply in the middle of a digest cycle)?
As a high level introduction I would say that it is rarely required to actually initiate your own digest cycle, since angular handles most cases.
That being said let's dive into the question.
As a very high level, the $digest loop looks like this:
Do:
- - - If asyncQueue.length, flush asyncQueue.
- - - Trigger all $watch handlers.
- - - Check for "too many" $digest iterations.
While: ( Dirty data || asyncQueue.length )
So basically $evalAsync is adding the function to the asyncQueue and defering a digest if it needs to. However if it's already in a digest cycle it will flush the asyncQueue and it will just call the function.
You may notice that this is very similar to the safeApply. One difference is that instead of adding the function to the asyncQueue it just calls it, which can happen in the middle of a cycle for instance. The other difference is that it exposes and relies on a $$ variable, which are intended to be internal.
The most important difference however between $evalAsync and $apply from $digest (I will get to $timeout below) is that $evalAsync and $apply starts the digest at the $rootScope but you call the $digest on any scope. You will need to evaluate your individual case if you think you need this flexibility.
Using $timeout is very similar to $evalAsync again except that it will always defer it out of the current digest cycle, if there is one.
$timeout(function() {
console.log( "$timeout 1" );
});
$scope.$evalAsync(function( $scope ) {
console.log( "$evalAsync" );
});
If you already in a digest cycle will give you
$evalAsync
$timeout 1
Even though they are called in the opposite order, since the timeout one is delayed to the next digest cycle, which it instantiates.
EDIT For the questions in the comment.
The biggest difference between $apply and $evalAsync as far as I can tell is that $apply is actually triggering the $digest cycle. To you all this means is that you need to be sure you call $apply when you aren't in a digest cycle. Just for transparency, the following code is also valid:
$scope.$apply(function() {
$scope.$evalAsync(function() {
});
});
Which will call the apply function, add the no-op function to the $asyncQueue and begin the digest cycle. The docs say it is recommended to use $apply whenever the model changes, which could happen in your $evalAsync
The difference between fn(); $scope.apply() and $scope.apply(fn) is just that the $scope.apply(fn) does a try catch for the function and explicitly uses $exceptionHandler. Additionally, you could actually attempt to cause digest cycles in fn, which will change how the apply is handled.
I'd also like to point out at this time that it is even more complicated in 1.3 assuming it stays this way. There will be a function called $applyAsync used to defer apply calls (reference).
This post compiled some information from This blog and this so post plus some of my experience.
Hope this helped!
There's only one way to start a digest cycle: Calling $digest. It's rarely called directly.
$apply is essentially a safe wrapper for $digest. Safe means that it handles errors. So most of the time this is the method you want to call to propagate changes. Another important difference is that it always starts a digest cycle at the root scope, whereas $digest can be called on any scope and only affects that scope and its descendants.
Many services and directives trigger a digest cycle, usually by calling $apply. The only important thing to know is wether you have to call $apply yourself or it has already been called by the service / directive.
Is safeApply still safe?
Yes, because there's still only one way to start a digest cycle.
What alternative we have instead of that safeApply() (in case we call $apply in the middle of a digest cycle)?
Not calling $apply in the middle of a digest cycle. Seriously. Being in that situation is almost always a design issue in the first place. It's better to resolve that issue instead of covering it.
Anyway the method used by the solution, checking $$phase, is the only way of knowing if a digest cycle is running. As shown by you it's even used by Angular internally. It's enough to do if (!$rootScope.$$phase). But keep in mind that it's a hack that might break with a new version of Angular. BTW: Monkey patching the topmost scope, as proposed by the author, would make the solution useless for isolated scopes.
Angular's way of data binding might indeed be difficult to understand for someone new to Angular at first. But basically it all boils down to this: For every change to be recognized / processed someone has to call $apply (or $digestfor that matter). That's it. This is supposed be done by directives and services. It's actually quite hard to get a '$apply already in progress' error if you have done everything right.
I've been told that using angular events can be expensive (I've been unable to verify this)
Any calls to $broadcast and $on should be 'wrapped' with a factory or service to inject into their corresponding components to preserve performance?
Again I'd rather use $on and listen directly to the events being fired rather than creating a factory that in essence is just going to register functions to call when it receives the event - lets call this a dispatcher.
Please note that it's not just one component (directives) listening to 'some-event' there will be a variety of components listening to this event.
Example dispatcher:
angular.module('app').factory('dispatcher', ['$rootScope', function ($rootScope) {
var registeredFns = [ ];
$rootScope.$on('some-event', function (evt, msg) {
_.each(registeredFns, function (fn) {
fn.apply(null, msg);
});
});
return {
onSomeEvent: function (fn) {
registeredFns.push(fn);
}
};
});
And inject it where I need it - maybe a directive, maybe a controller where ever it doesn't matter.
They're not expensive at all... but they're potentially expensive if they're misused (like anything else in life - this sounds like the start of a good joke!)
It's important to understand what's actually happening in here, and the code is actually pretty straightforward. If you call $emit(), it just does a for() loop across the array of registered listeners and calls each one. It does this on the scope you first call it on, and then walks "up" each parent until it hits $rootScope.
There's a little bit of extra code to handle things like stopPropagation, but otherwise that's it.
$broadcast does something similar, but in the opposite direction: it does this for() loop but then goes through every child scope. That's a super important difference, because if you do a $rootScope.$broadcast() and you have a lot of ngRepeats and directives floating around, you could be doing a huge amount more work than you expect - even if none of those scopes listen for this event, Angular still has to go through them all to figure that out.
Your fastest use-case if you want to do the fastest possible test in jsPerf or similar would be to use $rootScope.$emit() and $rootScope.$on(). That will give you a single-scope test for basic message passing and you can go from there.