AngularJS "$digest already in progress" Error: Diagnosis, Fixes, and Escalation
When AngularJS throws the "$digest already in progress" error, the UI can freeze or show incomplete updates. This guide walks through symptom recognition, root‑cause analysis, ordered checks, fixes, and escalation criteria to help you diagnose and resolve the issue safely.
17 Jul 2026, 05:52 UTC

Problem Statement
When an AngularJS application throws the "$digest already in progress" exception, the UI may freeze or show incomplete updates. This error indicates that a $digest cycle (the internal loop that processes $watch expressions) was started while another cycle was still running.
Recognizable Symptoms
- Console error:
TypeError: $digest already in progress(often followed by… at $rootScope.$apply) - Partial or no UI changes after a user action or async event
- Occasional UI stalls that disappear after a page refresh
- In automated tests, occasional flaky failures without visible test errors until console output is captured
Root Cause Analysis
The error arises when $apply or $digest is invoked while $rootScope.$$phase is already set to "$digest" or "$apply". Common triggers include:
- Direct
$applycalls inside$watchcallbacks,$timeout,$httpresponses, or directive link functions that are executed during a digest. - Custom directives that call
$applyinside theirlinkor controller logic when the directive is compiled as part of a larger digest. - Nested component loading where the framework automatically starts a digest for each compiled component.
Diagnostic Checklist
- Check the console for the exact error message. Note the stack trace; it often points to a custom
$applycall. - Inspect
$rootScope.$$phaseat the time of the error. Addconsole.log('$rootScope.$$phase', $rootScope.$$phase);near the suspected$applycall. - Locate any direct
$applyor$digestcalls. Search the codebase for$applyusage, especially inside callbacks or directive link functions. - Determine if the call is inside a digest. If
$rootScope.$$phaseis truthy, you are already in a digest. - Identify async triggers. Look for
$http,$timeout,$interval, or event listeners that may invoke$applyafter the digest has begun.
Ordered Fixes
- Replace direct
$applywith$evalAsyncor$applyAsync. These schedule the update for the next digest or batch multiple updates, preventing reentrancy.// Bad $scope.$apply(() => { $scope.value = newValue; }); // Good $scope.$evalAsync(() => { $scope.value = newValue; }); - Use
$rootScope.$applyAsyncfor global updates. This defers the digest until the current call stack unwinds.$rootScope.$applyAsync(() => { $rootScope.global = data; }); - Wrap risky code in
try/catchand log$$phase. This helps confirm that the digest was already in progress.try { $scope.$apply(); } catch (e) { console.warn('Digest already in progress', e); } - Avoid calling
$applyinside$watchcallbacks. Instead, use$evalAsyncor move the logic outside the watch. - For directives, use
linkorcontrollerfunctions that schedule updates asynchronously. Example:link: function(scope, element) { element.on('click', () => { scope.$evalAsync(() => { scope.clicked = true; }); }); }
Verification Steps
- Reproduce the error. Add a
$timeout(() => $scope.$apply(), 0)inside a controller that is bound to a view. Open the console and confirm the error appears. - Apply the fix. Replace the direct
$applywith$evalAsync. - Re-run the scenario. Verify that the console no longer shows the error and the UI updates normally.
- Use Chrome DevTools Performance tab. Record a digest cycle before and after the fix. Confirm that no nested
$digestcalls occur after the change.
Escalation Criteria
- If the error persists after refactoring all direct
$applycalls, the issue may stem from a third‑party library that triggers AngularJS internally. Check the library’s documentation for AngularJS compatibility notes. - When the application is large and the error appears intermittently, consider instrumenting
$rootScope.$$phaseglobally to capture all digest starts and ends, and analyze the stack traces to locate hidden triggers. - In a CI environment, enable console error capturing in the test runner (e.g., Karma). Treat any console error as a test failure to surface hidden digest reentrancy.
Limitations and Caveats
- AngularJS 1.x is end‑of‑life; future projects should migrate to Angular (2+). This guide applies only to existing AngularJS 1.x codebases.
- Replacing
$applywith$evalAsyncmay delay UI updates by a micro‑tick. For critical real‑time interactions, validate that the delay is acceptable. - Some legacy code may rely on the synchronous nature of
$apply. In those cases, a careful review of the surrounding logic is required before refactoring.
Practical Checklist for Engineers
| Step | What to Do | Why It Matters |
|---|---|---|
| 1 | Search for $apply in callbacks | Direct calls inside callbacks are the most common culprit. |
| 2 | Replace with $evalAsync | Schedules update after current digest, preventing reentrancy. |
| 3 | Add console.log('$rootScope.$$phase') around suspect code | Immediate visual confirmation of digest state. |
| 4 | Run the app and monitor console errors | Ensures the fix is effective. |
| 5 | Record performance to confirm no nested digest cycles | Verifies complete elimination of the issue. |
Conclusion
The "$digest already in progress" error is a symptom of re‑entrant digest cycles. By systematically locating direct $apply calls inside digest‑aware contexts and replacing them with $evalAsync or $applyAsync, you can restore stable UI updates and eliminate the error. Use the provided diagnostic steps to confirm the fix and avoid regressions in future code changes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.