Angular’s change detection used to rely on Zone.js, a library that tracks and intercepts asynchronous operations such as events, HTTP requests, and timers. It creates execution contexts (called "zones") around blocks of code. Its key superpower: it monkey-patches (dynamically overrides at runtime) many of the browser's native asynchronous APIs.
Commonly patched APIs include:
-
setTimeout/setInterval/clearTimeout -
Promise.then/Promise.catch/async/await(via promise rewriting) - DOM event listeners (
addEventListenerfor click, input, etc.) -
XMLHttpRequest/fetch - Many others (e.g.,
requestAnimationFrame, WebSocket callbacks, etc.)
When you call these patched APIs inside an Angular app, Zone.js wraps them so it can:
- Know when the async task starts
- Know when it finishes (or errors)
- Track pending microtasks/macrotasks
How Zone.js + NgZone Worked Step by Step
Bootstrap & Zone Creation
When your Angular app starts (platformBrowser().bootstrapModule(AppModule)), Angular loads Zone.js and creates a special child zone called NgZone (viaNgZoneservice). Your entire app code runs inside this NgZone.Monkey-Patching Happens
Zone.js overrides browser APIs globally (or within the zone).
Example: When you writesetTimeout(() => { this.count++ }, 1000), Zone.js actually runs something like
// Simplified pseudo-code of what Zone.js does
const originalSetTimeout = window.setTimeout;
window.setTimeout = function(callback, delay) {
// Zone tracks this task
const task = Zone.current.scheduleMacroTask('setTimeout', callback, ...);
return originalSetTimeout(task.callbackWrapper, delay);
};
-
Async Operation Occurs
- User clicks a button → patched
addEventListenerfires → handler runs inside NgZone - HTTP request completes → patched
XMLHttpRequestcallback runs - Timer fires → patched
setTimeoutcallback runs
- User clicks a button → patched
-
Zone Notifies Angular
- Zone.js emits events at key moments, especially when the microtask queue becomes empty (
onMicrotaskEmpty) after async work. - In
ApplicationRef→ subscribes toNgZone.onMicrotaskEmpty→ Calls tick() → runs full change detection cycle.
- Zone.js emits events at key moments, especially when the microtask queue becomes empty (
-
Change Detection Runs
Angular traverses the component tree (from root down):- Performs dirty checking: compare current vs previous bound values
- Update DOM only where differences exist
- In dev mode: runs twice (to catch inconsistencies / side-effects)
- With
ChangeDetectionStrategy.OnPush, it skips subtrees unless inputs change or events fire
This loop repeats after almost any async boundary — making the UI update "magically" after clicks, HTTP responses, timers, etc.
References:
- https://dev.to/vivekdogra02/angular-zonejs-change-detection-understanding-the-core-concepts-16ek
- https://angular.love/from-zone-js-to-zoneless-angular-and-back-how-it-all-works
- https://learnwithawais.medium.com/understanding-zone-js-and-zoneless-detection-in-angular-c1ca58d380a3
- https://www.youtube.com/watch?v=lmrf_gPIOZU&t=126s
- https://jeanmeche.github.io/angular-change-detection/
- https://stackblitz.com/edit/kobi-hari-udemy-angular-ngrx-signals-obnnh8?title=Change%20Detection%20OnPush&file=src/app/app.component.ts
Top comments (0)