Suggest an editImprove this articleRefine the answer for “What is ngzone in Angular?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NgZone** is Angular's execution context built on Zone.js that automatically triggers change detection when async tasks complete inside the zone. ```typescript this.ngZone.runOutsideAngular(() => { setInterval(() => this.counter++, 16); // No CD triggered }); this.ngZone.run(() => { this.data = freshData; }); // CD triggered once ``` **Key point:** Zone.js patches browser APIs (setTimeout, Promises, events) so Angular intercepts task completion. Use `runOutsideAngular()` for heavy loops that skip UI updates.Shown above the full answer for quick recall.Answer (EN)Image**NgZone** is Angular's wrapper around Zone.js that patches browser async APIs to automatically trigger change detection when tasks complete inside the Angular zone. ## Theory ### TL;DR - NgZone acts like a motion sensor: any async activity inside it (setTimeout, fetch) tells Angular to check the UI for updates. - Inside the zone, Angular runs change detection automatically. Outside, it ignores async results entirely. - Use `runOutsideAngular()` for heavy loops that don't touch the UI. Use `run()` to bring third-party callbacks back in. - Zone.js patches 40+ browser APIs at bootstrap so Angular can intercept task completion. - Angular 17+ supports zoneless mode with signals as an alternative path. ### Quick example ```typescript import { Component, NgZone, OnInit } from '@angular/core'; @Component({ selector: 'app-demo', template: `<p>{{count}}</p>` }) export class DemoComponent implements OnInit { count = 0; constructor(private ngZone: NgZone) {} ngOnInit() { // Inside zone: Angular detects the change and updates the template setTimeout(() => this.count++, 1000); // Outside zone: count increments in memory, template stays at 0 this.ngZone.runOutsideAngular(() => { setTimeout(() => this.count++, 1000); }); } } ``` The first `setTimeout` is intercepted by Zone.js, so Angular knows to run change detection when it finishes. The second runs in a forked zone Angular does not watch. ### How it works internally Zone.js replaces native browser APIs at bootstrap. When your code calls `window.setTimeout`, it actually calls Zone.js's version, which forks a child zone, runs the callback, and notifies Angular's `NgZoneImpl` on completion. Angular then schedules `ApplicationRef.tick()` via `queueMicrotask`, which walks the component tree and checks for updates. In dev mode, Angular checks the full component tree. With `OnPush` components, it only checks marked subtrees. Either way, the trigger is the same: a task completing inside the zone. Third-party WebSocket callbacks, native `requestAnimationFrame`, and many library events run outside the Angular zone by default. Zone.js does not patch every async path. That is where most NgZone bugs originate. ### When to use - Heavy animation loops and `setInterval` counters that update UI only at the end: `runOutsideAngular()` stops Angular from running change detection 60 times per second. - WebSocket or third-party library callbacks that need to update the template: wrap in `ngZone.run()` to bring them into the zone. - Server-side rendering with Angular Universal: check `NgZone.isInAngularZone()` before DOM mutations to avoid hydration mismatches. - Any async that updates `@Input` or `@Output` bindings: stay inside the zone (the default behavior). ### Common mistakes **Mistake 1: Assuming WebSocket callbacks are inside the zone** ```typescript // Wrong: template never updates const socket = new WebSocket('ws://api.example.com/data'); socket.onmessage = (event) => { this.data = event.data; // Zone.js does not fully patch WebSocket }; // Correct socket.onmessage = (event) => { this.ngZone.run(() => { this.data = event.data; }); }; ``` The first time this caught me off guard was a real-time chart where WebSocket data arrived but nothing updated. The array was growing in memory. The change detector was never told to look. **Mistake 2: Using `runOutsideAngular()` on code that updates the UI** ```typescript // Wrong: counter freezes in the template this.ngZone.runOutsideAngular(() => { setInterval(() => this.counter++, 1000); }); // Correct: re-enter zone on each update this.ngZone.runOutsideAngular(() => { setInterval(() => { this.counter++; this.ngZone.run(() => {}); // Force a tick }, 1000); }); ``` **Mistake 3: Not setting up intervals outside the zone from the start** ```typescript // Wrong: zone tracks the pending task the whole time ngOnInit() { this.timer = setInterval(() => this.frameCount++, 16); // Inside zone } ngOnDestroy() { clearInterval(this.timer); // Zone was watching it the entire time } // Correct: create outside zone, it never gets tracked ngOnInit() { this.ngZone.runOutsideAngular(() => { this.timer = setInterval(() => this.frameCount++, 16); }); } ngOnDestroy() { clearInterval(this.timer); } ``` **Mistake 4: Nested `run()` calls in Angular 16+** Calling `ngZone.run()` from code already inside the zone nests zones and triggers double `tick()` calls. Check first: ```typescript const update = () => { this.value = newValue; }; if (this.ngZone.isInAngularZone()) { update(); } else { this.ngZone.run(update); } ``` ### Real-world usage - NG Bootstrap: `NgbModal` uses `runOutsideAngular()` for animation frames to hit 60fps. - PrimeNG DataTable: virtual scroll handlers run outside the zone to avoid change detection on every scroll event. - Angular Material CDK: overlay events go through `NgZone.run()` when overlays open or close. - RxJS in services: `tap(() => this.ngZone.run(() => this.update()))` when an observable originates outside the zone. - Angular Universal SSR: `isBrowser ? ngZone.run(...) : directDomCall(...)` guards against zone mismatches. `ChangeDetectorRef.detectChanges()` is the component-local alternative. It is faster because it does not walk the whole tree. Use `NgZone` when the trigger comes from a service or a cross-component async source. ### Follow-up questions **Q:** How does Zone.js patch `setTimeout` and `addEventListener`? **A:** Zone.js replaces `window.setTimeout` with a wrapper that calls `currentZone.runGuarded(fn)`. For `addEventListener`, it wraps the handler in a proxy that forks a child zone on dispatch. Both notify `NgZoneImpl` when the task completes. **Q:** What is the performance cost of staying inside the Angular zone? **A:** Each `ApplicationRef.tick()` call costs 10-50ms on large apps because it walks the component tree. Running hot loops outside the zone avoids those ticks entirely, but any UI update then requires a manual `run()`. **Q:** In Angular 17+ with signals, does NgZone still matter? **A:** Signals use a scheduler instead of zone-based patching, so zoneless apps can skip Zone.js completely. If you have legacy zone-based code or third-party libraries that expect zones, NgZone stays relevant as a bridge between the two models. **Q:** In an `OnPush` component with `async` pipe, does NgZone affect change detection? **A:** The `async` pipe marks the component dirty via `ChangeDetectorRef.markForCheck()`, not via zone. If an observable comes from outside the zone, the `async` pipe alone is not enough. You still need `ngZone.run()` to schedule the tick that reads the dirty flag. **Q:** (Senior) How do you debug a frozen UI after upgrading a third-party library? **A:** In browser devtools, run `ng.probe($0).injector.get(NgZone).isInAngularZone()` while the app processes data. If it returns `false`, the library callback runs outside the zone. Wrap it in `ngZone.run()` or switch to a version with native Angular integration. ## Examples ### WebSocket dashboard ```typescript import { Component, NgZone, OnDestroy } from '@angular/core'; @Component({ selector: 'app-dashboard', template: `<ul><li *ngFor="let d of data">{{d}}</li></ul>` }) export class DashboardComponent implements OnDestroy { data: number[] = []; private socket: WebSocket; constructor(private ngZone: NgZone) { this.socket = new WebSocket('ws://api.example.com/metrics'); // WebSocket onmessage runs outside Angular zone by default this.socket.onmessage = (event) => { this.ngZone.run(() => { // Now inside zone: Angular sees the change and re-renders the list this.data.push(+event.data); }); }; } ngOnDestroy() { this.socket.close(); } } // Result: new values appear in the template as they arrive ``` Without `ngZone.run()`, the array grows in memory but the template never re-renders. This is the most common NgZone bug in production dashboards. ### Infinite scroll with large lists ```typescript import { Component, NgZone } from '@angular/core'; @Component({ selector: 'app-list', template: ` <div *ngFor="let item of items">{{item}}</div> <p *ngIf="loading">Loading...</p> ` }) export class InfiniteListComponent { items: string[] = []; loading = false; constructor(private ngZone: NgZone) { window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { this.loadMore(); } }); } private async loadMore() { this.loading = true; const newItems = await fetchMore(); // Promise runs outside zone // Re-enter zone only when data is ready, not on every scroll pixel this.ngZone.run(() => { this.items.push(...newItems); this.loading = false; }); } } // Result: smooth scroll on 10k+ item lists, CD fires only when data arrives ``` Change detection runs once per load batch. Not on every scroll event. ### Progress bar with heavy computation ```typescript import { Component, NgZone } from '@angular/core'; @Component({ selector: 'app-progress', template: `<div [style.width.%]="progress"></div>` }) export class ProgressComponent { progress = 0; constructor(private ngZone: NgZone) {} startHeavyTask() { // 1000 animation frames outside zone, one final render at the end this.ngZone.runOutsideAngular(() => { let i = 0; const tick = () => { i++; this.progress = i / 10; // Updates internal value only if (i < 1000) { requestAnimationFrame(tick); } else { // Back inside zone for the final render this.ngZone.run(() => { this.progress = 100; }); } }; requestAnimationFrame(tick); }); } } // Result: 0 change detection cycles during 1000 frames, 1 final render ```For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.