Zone.js and change detection optimization in Angular?
understanding Angular's async tracking and performance tuning.
zone.js wraps async (timers, events, HTTP), triggering change detection after each; use runOutsideAngular when external libraries fire high-frequency events.
WHY IT EXISTS: Angular's change detection runs after every async event (click, timer, HTTP response). This catches all state changes but is expensive if events are frequent. Zone.js is the mechanism; NgZone is the knob to control it. High-frequency events (scroll, mouse move, resize, websocket messages from a charting library) trigger detection 60+ times per second, draining the main thread.
THE MENTAL MODEL: Zone.js monkey-patches async APIs (setTimeout, addEventListener, fetch, etc.) globally. When async code finishes, zone.js notifies Angular to run change detection. For most events (button clicks, form input), this is right. For noisy events (pointer move on a canvas, websocket ticks from a live chart), it's wasteful.
HOW IT WORKS: By default, all async runs inside Angular's zone. On completion, zone notifies Angular's change detection scheduler, which runs in the next microtask. If a third-party charting library fires mousemove events 60 times per second, each triggers change detection, freezing the main thread. Solution: wrap the library's event handlers in NgZone.runOutsideAngular(() => { ... }). This executes the code outside the zone; completions don't trigger detection. If the library mutates component state, manually trigger detection afterward with NgZone.run().
WHEN IT MATTERS: High-frequency sensors (geolocation, accelerometer), real-time rendering (maps, 3D), and noisy external libraries (D3, Cesium) are common culprits. Change detection isn't the bottleneck in simple apps, but high-traffic dashboards often struggle here.
ONE CONCRETE EXAMPLE: A Mapbox map overlay. Mapbox fires mousemove events constantly. By default, each triggers Angular change detection, slowing the map. Solution: }
@Component({ ... }) export class MapComponent { constructor(private ngZone: NgZone) {} ngOnInit() { this.ngZone.runOutsideAngular(() => { map.on('mousemove', (e) => { /* update map internal state */ }); }); } }
Now mousemove doesn't trigger Angular's change detection. If the handler updates component state (e.g., coordinates displayed in a panel), wrap that in this.ngZone.run().
Read the original → angular.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.