The error $apply already in progress
in AngularJS occurs when you attempt to manually trigger a $digest
cycle using $scope.$apply()
while AngularJS is already in the middle of running a $digest
cycle. AngularJS runs its $digest
cycle automatically when certain actions (like model updates, events, or external asynchronous functions) trigger the view to be updated. If you manually call $apply()
while AngularJS is already processing the $digest
cycle, it will throw this error to prevent an infinite loop or conflicting changes.
Step 1: Understanding $apply
and $digest
in AngularJS
In AngularJS, $digest
is the process where the framework checks all watched variables (scope variables bound to the view) to see if their values have changed. When a change is detected, Angular updates the view to reflect the model’s new state.
$scope.$apply()
: This method is used to trigger a$digest
cycle manually when changes happen outside of Angular’s normal context (for example, changes made in third-party libraries or asynchronous events like HTTP requests).$digest
cycle: Angular automatically runs this cycle when the view needs to be updated, either after user interactions, timeouts, or events.
If you call $scope.$apply()
inside the middle of a $digest
cycle, AngularJS will throw the $apply already in progress
error to prevent the cycle from triggering multiple times.
Step 2: Causes of $apply already in progress
Here are the common reasons why you might encounter this error:
- Calling
$apply()
Inside Another$digest
Cycle: When AngularJS is already running its$digest
cycle, triggering$apply()
will cause AngularJS to enter another$digest
cycle within the ongoing one, which leads to this error. - Calling
$apply()
InsidesetTimeout
orsetInterval
: If you use$scope.$apply()
inside asetTimeout()
orsetInterval()
function, and these are triggered during the$digest
cycle, Angular will detect that$apply()
is being invoked while a$digest
cycle is already running. - Using
$apply()
Inside an Asynchronous Callback: If you invoke$apply()
inside an asynchronous callback (like in an HTTP request or a callback from a third-party library) that gets executed while the$digest
cycle is ongoing, the error will occur. - Nested
$apply()
Calls: If$apply()
is called inside a function that’s itself inside a$scope.$apply()
or$timeout()
, this will lead to nested$apply()
calls, causing the error.
Step 3: How to Fix the $apply already in progress
Error
1. Avoid Calling $apply()
Inside Another $apply()
or $digest
Cycle
Ensure you don’t call $apply()
from within another $apply()
or $digest
cycle. For example, if you are calling $apply()
manually after an event or timeout, make sure it’s not happening during an already-running cycle.
// Incorrect: Calling $apply inside another $apply
$scope.$apply(function() {
$scope.$apply(function() {
$scope.someVar = 'value'; // This will throw $apply already in progress error
});
});
2. Use $evalAsync()
for Asynchronous Updates
If you need to update the model asynchronously but don’t want to trigger an additional $digest
cycle, use $scope.$evalAsync()
. This method schedules an expression to be evaluated in the next $digest
cycle, rather than immediately triggering a new cycle.
$scope.someFunction = function() {
$scope.$evalAsync(function() {
// Update the model asynchronously without triggering an additional $digest cycle
$scope.someVar = 'new value';
});
};
Using $evalAsync()
ensures that the update is handled in the next $digest
cycle rather than causing nested cycles.
3. Use $timeout()
Instead of $apply()
Instead of manually calling $apply()
, you can use $timeout()
to delay the execution of the code and allow Angular to run the $digest
cycle before running the code. $timeout()
automatically triggers the $digest
cycle and handles this for you.
$timeout(function() {
$scope.someVar = 'new value'; // $timeout ensures digest cycle handling
}, 0);
This ensures that Angular will handle the update properly without conflicting with the existing $digest
cycle.
4. Check for Nested $timeout()
Calls
Ensure that $timeout()
isn’t being used in a nested manner, which could cause the $apply already in progress
error.
$timeout(function() {
$scope.someVar = 'new value';
$timeout(function() {
// This could cause a nested $apply problem
$scope.someVar2 = 'another value';
}, 0);
}, 0);
5. Make Sure $apply()
Is Not Called in Event Listeners
If $apply()
is called inside an event listener (such as $scope.$on()
), and the event is triggered while the $digest
cycle is already running, you’ll get the $apply already in progress
error.
$scope.$on('someEvent', function(event, data) {
$scope.$apply(function() {
// This will cause $apply already in progress error if event is triggered inside an ongoing digest cycle
$scope.someVar = data;
});
});
To fix this, use $evalAsync()
or $timeout()
instead of $apply()
.
6. Use $rootScope
for Global Changes
For global changes to the model, instead of using $apply()
directly on a local scope, consider using $rootScope
to trigger changes across the application.
$rootScope.$apply(function() {
// Update model globally
$scope.someVar = 'new value';
});
This helps avoid conflicts between nested $apply()
calls on individual controllers or scopes.
Step 4: Debugging the Issue
- Log the Execution Flow: Add
console.log()
statements in your$apply()
and$digest
code to ensure that it is not being triggered unexpectedly or in an overlapping context.
console.log('Before $apply');
$scope.$apply(function() {
console.log('Inside $apply');
$scope.someVar = 'new value';
});
console.log('After $apply');
This will help you identify where the $apply()
call is being made and how the cycle is running.
- Track
$digest
Cycle Triggers: Check whether the$digest
cycle is running multiple times and whether any asynchronous code is involved in the triggering. You can use$scope.$$phase
to check if a$digest
cycle is already in progress.
if (!$scope.$$phase) {
$scope.$apply(function() {
$scope.someVar = 'new value';
});
}
This ensures that $apply()
is only called if the $digest
cycle is not already in progress.
Step 5: Final Considerations
- Avoid Manual
$apply()
in Most Cases: AngularJS’s automatic handling of$digest
cycles works in most situations. Use$apply()
sparingly and prefer$timeout()
,$evalAsync()
, or$scope.$watch()
when appropriate. - Async Code Handling: When using third-party libraries, AJAX requests, or
setTimeout
, make sure that the model changes are handled asynchronously to avoid disrupting AngularJS’s digest cycle. - Event Handling: When updating scope variables in response to events or callbacks, make sure the
$digest
cycle is triggered correctly without causing conflicts by using$evalAsync()
.