The Digest Cycle
What $digest and $apply do, and why later frameworks moved away from this model.
What you'll learn
- Explain what the digest cycle checks for and why it exists
- Describe when $apply is needed to trigger a digest manually
- Explain why later frameworks moved away from AngularJS's digest-cycle model
Explanation
The digest cycle is AngularJS's mechanism for detecting changes and updating the DOM -- conceptually similar in purpose to change detection in modern Angular (if you've taken that course), but implemented very differently. AngularJS registers a watcher for every binding/expression it needs to track, and $digest repeatedly re-checks every registered watcher's current value against its previously-recorded value ("dirty checking"), updating the DOM for anything that changed -- and re-running the whole check again if anything did change, until nothing changes anymore or a maximum iteration count is hit.
AngularJS automatically triggers $digest after things it's aware of (an ng-click handler, an ng-model-bound input event, an AngularJS-provided $http response). The trouble comes when your code changes $scope data from outside something AngularJS is watching for -- a raw setTimeout, a third-party library's callback, a native (non-AngularJS) event listener. In those cases, AngularJS has no idea a change happened, and the view silently doesn't update until something else happens to trigger a digest. The fix is calling $scope.$apply(() => { /* your change */ }) explicitly, which makes the change and then manually triggers a digest.
This dirty-checking-every-watcher approach is exactly the kind of implementation detail that becomes a real performance concern as an application's watcher count grows (a large ng-repeat-heavy page can register thousands of watchers), and it's one of the concrete, well-documented reasons later frameworks (modern Angular, React, Vue) moved to different change-detection/reactivity models entirely.
Guided lab
Predict: Dirty checking detecting a changed watcher
This models one digest pass: check every watcher's current value against its last-recorded value, and report which ones changed. Predict the output.
const watchers = [
{ name: "username", lastValue: "ada", getCurrentValue: () => "ada" },
{ name: "count", lastValue: 3, getCurrentValue: () => 5 },
{ name: "isValid", lastValue: false, getCurrentValue: () => false },
];
for (const watcher of watchers) {
const current = watcher.getCurrentValue();
if (current !== watcher.lastValue) {
console.log(`${watcher.name} changed: ${watcher.lastValue} -> ${current}`);
}
}Stuck? Get a hint.
Common mistakes
- Changing $scope data inside a raw setTimeout or third-party callback and being confused why the view doesn't update -- AngularJS doesn't know to run a digest unless you call $scope.$apply().
- Calling $scope.$apply() when already inside a digest cycle (e.g. inside an ng-click handler, which already triggers one), causing a 'digest already in progress' error.
- Assuming digest-cycle performance issues are rare -- a large ng-repeat list can register enough watchers to make this a real, measurable problem.
Knowledge check
Takeaway
The digest cycle dirty-checks every registered watcher -- call $scope.$apply() when changing $scope from outside AngularJS's awareness, and expect real performance costs from large watcher counts.
Summary
The digest cycle repeatedly dirty-checks watchers to detect changes; $apply manually triggers it for changes AngularJS wouldn't otherwise notice; watcher count is a genuine performance concern at scale.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.