Angular Learning
Nền tảng Angular

Zone.js chi tiết

Hiểu Zone.js bọc async API ra sao, Angular dùng nó để đánh thức change detection như thế nào và cách làm việc với NgZone.

Phạm vi bài này

Bài này giải thích cơ chế Zone.js trong các ứng dụng Angular truyền thống. Với Angular v21 trở lên, ứng dụng mới mặc định chạy zoneless; tuy vậy Zone.js vẫn rất quan trọng khi bạn bảo trì dự án Angular cũ hoặc thư viện còn phụ thuộc vào NgZone.

Mục lục

Zone.js sinh ra để giải quyết vấn đề gì?

JavaScript chạy bất đồng bộ qua nhiều API: click của người dùng, setTimeout, Promise, HTTP response hoặc callback của thư viện. Sau khi một callback đổi state, framework cần biết lúc nào phải đồng bộ state đó ra DOM.

Nếu không có cơ chế hỗ trợ, mỗi callback phải tự báo framework:

setTimeout(() => {
  this.message = 'Đã tải xong';
  // Framework cần được báo để kiểm tra lại template.
}, 1_000);

Điều này vừa lặp lại vừa dễ quên. Zone.js cung cấp một lớp quan sát chung quanh nhiều API async. Angular truyền thống dùng lớp này để nhận một thông báo đại ý: “một callback của ứng dụng vừa hoàn thành; có thể cần kiểm tra giao diện.”

Đừng nhầm vai trò

Zone.js không quản lý state, không thay đổi HTML và không hiểu binding Angular. Nó chỉ giúp Angular biết thời điểm async work có thể làm state thay đổi.

Zone.js làm việc như thế nào?

Angular zone là gì?

Trong ứng dụng còn dùng Zone.js, Angular tạo một zone con riêng và theo dõi zone đó. Zone con này được gọi ngắn gọn là Angular zone. NgZone là service Angular dùng để tạo và làm việc với nó.

Khi Angular khởi động ứng dụng, nó chạy phần tạo component đầu tiên, render đầu tiên và lifecycle hook bên trong Angular zone. Hãy hình dung gần đúng như sau; đây không phải mã nguồn Angular thật:

const ngZone = new NgZone();

ngZone.run(() => {
  // Angular tạo và khởi động ứng dụng ở đây.
  // Component, template và ngOnInit() bắt đầu từ đây.
  startAngularApplication();
});

ngZone.run(...) không tạo thread mới, không tạo một JavaScript runtime khác và không bọc từng dòng TypeScript của bạn. Nó chỉ đặt Zone.current thành Angular zone trong lúc hàm bên trong đang chạy. Callback async được đăng ký trong lúc đó sẽ được Zone.js ghi nhớ là callback thuộc Angular zone.

Nói đơn giản: Angular zone là “khu vực callback mà Angular muốn nghe ngóng”. Nếu callback chạy trong khu vực này và hoàn tất, Angular có thể biết để lên lịch change detection. Callback chạy ngoài khu vực này thì Angular không nhận notification theo cơ chế Zone.js.

Chỉ áp dụng cho ứng dụng dùng Zone.js

Ở ứng dụng zoneless, không có Angular zone theo nghĩa Zone.js này để đánh thức change detection. Angular dựa vào signal, AsyncPipe, event template hoặc markForCheck() thay vì theo dõi async task qua zone.

Một app có chỉ một Angular zone không?

Với một ứng dụng Angular thông thường được bootstrap một lần, có thể hình dung nó có một NgZone chính, tức một Angular zone chính để Angular theo dõi. Phần lớn code component chạy trong zone này, nên phần lớn timer, Promise và event callback mà code đó đăng ký cũng được gắn với nó.

Nhưng đừng kết luận “cả app chỉ có đúng một zone” hoặc “mọi callback async chắc chắn thuộc Angular zone”. Zone.js còn có root zone và có thể có các zone con khác. Quan trọng hơn, các trường hợp sau nằm ngoài Angular zone hoặc không được theo dõi theo cách đó:

  • Code được đăng ký trong ngZone.runOutsideAngular().
  • Callback do thư viện khởi tạo ngoài Angular zone.
  • API không được Zone.js patch như bạn mong đợi.
  • Ứng dụng chạy zoneless.

Nói ngắn gọn: một app Angular truyền thống thường có một Angular zone chính; callback được đăng ký khi đang ở trong zone đó sẽ kế thừa zone đó. Đây là lý do đa số setTimeout trong component tự làm mới UI, nhưng không phải quy luật đúng cho mọi callback.

Zone là ngữ cảnh chạy của callback

Cụm “ngữ cảnh chạy” có thể hiểu bằng luồng callback cụ thể. Zone.js không sửa source code component mỗi lần bạn gọi timer.

  1. Khi nạp polyfills, trước khi app chạy: zone.js patch API global một lần. Nó giữ setTimeout gốc rồi cài một hàm bọc vào window.setTimeout.
  2. Khi Angular bootstrap: Angular tạo NgZone rồi chạy phần khởi tạo ứng dụng qua ngZone.run(...). Vì vậy, trong lifecycle hook của component, Zone.current là Angular zone.
  3. Khi component gọi setTimeout(callback, 1000): wrapper của Zone.js chạy trước. Nó lấy Zone.current (Angular zone), tạo một task chứa callback của bạn và nhớ zone sở hữu task đó. Sau đó nó đưa wrapper callback cho timer gốc của trình duyệt.
  4. Khi timer hoàn tất: trình duyệt gọi wrapper callback. Wrapper dùng zone đã nhớ để chạy callback của bạn. Các hook của Angular zone vì thế biết task này bắt đầu/kết thúc.

Vì thế, Zone.js làm hai việc khác nhau: nó patch API global lúc polyfills được nạp, và nó lưu zone sở hữu cho từng callback lúc callback được đăng ký. Nó không sửa file TypeScript hay thân callback của bạn.

direction: right

n0: "Nạp app một lần\nThay global setTimeout\nbằng wrapper" {
  width: 200
  height: 79
}

n1: "Mỗi lần gọi timer\nWrapper nhớ zone +\ncallback, gọi timer\ngốc" {
  width: 176
  height: 100
}

n2: "Timer xong\nWrapper chạy callback\ntrong zone đã nhớ" {
  width: 192
  height: 79
}

n0 -> n1
n1 -> n2
Nạp app một lầnThay global setTimeoutbằng wrapperMỗi lần gọi timerWrapper nhớ zone +callback, gọi timergốcTimer xongWrapper chạy callbacktrong zone đã nhớ
Nạp app một lầnThay global setTimeoutbằng wrapperMỗi lần gọi timerWrapper nhớ zone +callback, gọi timergốcTimer xongWrapper chạy callbacktrong zone đã nhớ
Zone.js patch API và giữ context callback

Có thể hình dung gần đúng bằng mã giả sau:

// Đây là hàm Zone.js đã cài vào window.setTimeout lúc nạp polyfills.
function patchedSetTimeout(callback: () => void, delay: number) {
  const zoneLúcĐăngKý = Zone.current; // Khi component chạy: Angular zone.

  return nativeSetTimeout(() => {
    // Lúc timer xong: phục hồi ngữ cảnh đã nhớ rồi mới gọi callback.
    zoneLúcĐăngKý.run(callback);
  }, delay);
}

Zone.current và .run() trong ví dụ là API/ý tưởng của Zone.js. Mã thật phức tạp hơn, nhưng nguyên lý là vậy. Zone không là thread mới, không là biến global chứa state và không giữ dữ liệu component. Nó là “môi trường thực thi” để Zone.js biết callback này thuộc về ai và hook nào cần chạy trước/sau callback.

Vì sao wrapper phải nhớ zone?

Bạn hình dung đúng một phần: Zone.js bọc callback. Trong một ứng dụng đơn giản, ta có thể tưởng tượng wrapper làm việc này:

nativeSetTimeout(() => {
  callbackCủaBạn();
  báoAngularKiểmTraUI();
}, 1_000);

Nhưng Zone.js không thể luôn gọi báoAngularKiểmTraUI() như vậy. setTimeout là API global: cùng một hàm bị dùng bởi code Angular, code trong runOutsideAngular(), thư viện bên thứ ba, thậm chí zone con khác. Wrapper cần biết callback này thuộc zone nào để chỉ gọi hook của zone đó.

Reference được lưu chỉ là reference tới object zone — không phải reference tới component, state hay DOM. Object Angular zone chứa các hook Angular dùng để theo dõi task. Khi browser gọi wrapper sau một giây, browser không tự nhớ callback này trước đây được đăng ký ở Angular zone hay ngoài Angular zone; reference đó là thông tin Zone.js cần để khôi phục đúng ngữ cảnh.

direction: right

timer: "Patched setTimeout" {
  width: 168
  height: 58
}

inside: "Đăng ký trong\nAngular zone" {
  width: 130
  height: 58
}

outside: "Đăng ký trong\nrunOutsideAngular()" {
  width: 176
  height: 58
}

notify: "Wrapper nhớ Angular\nzone\nAngular có thể được\nbáo" {
  width: 176
  height: 100
}

silent: "Wrapper nhớ outer zone\nKhông báo Angular qua\nZone.js" {
  width: 200
  height: 79
}

timer -> inside
timer -> outside
inside -> notify
outside -> silent
Patched setTimeoutĐăng ký trongAngular zoneĐăng ký trongrunOutsideAngular()Wrapper nhớ AngularzoneAngular có thể đượcbáoWrapper nhớ outer zoneKhông báo Angular quaZone.js
Patched setTimeoutĐăng ký trongAngular zoneĐăng ký trongrunOutsideAngular()Wrapper nhớ AngularzoneAngular có thể đượcbáoWrapper nhớ outer zoneKhông báo Angular quaZone.js
Cùng patched API nhưng khác zone đăng ký

Ngay cả khi app thường chỉ có một Angular zone chính, reference vẫn cần thiết vì một callback có thể được đăng ký ngoài zone đó. Nó cũng giúp callback con được đăng ký bên trong callback cha tiếp tục kế thừa đúng ngữ cảnh.

Patch API nghĩa là gì?

Zone.js được nạp trước code ứng dụng — trong Angular truyền thống, thường qua zone.js ở polyfills. Ngay khi được nạp, nó giữ lại setTimeout gốc của trình duyệt rồi thay window.setTimeout bằng hàm bọc của nó. Tên API vẫn là setTimeout, nên component của bạn không cần gọi API đặc biệt nào.

Vì vậy Angular không cần “đoán người dùng vừa gọi setTimeout”. Khi code chạy:

setTimeout(callback, 1_000);

thì lời gọi đã đi vào wrapper cài từ trước. Wrapper chụp Zone.current, thay callback bằng callback đã bọc, sau đó mới gọi setTimeout gốc của trình duyệt:

// Mô hình đơn giản hóa — không phải mã nguồn Zone.js thật.
const nativeSetTimeout = window.setTimeout;

window.setTimeout = (callback, delay) => {
  const zoneLúcĐăngKý = Zone.current;

  return nativeSetTimeout(() => {
    zoneLúcĐăngKý.run(callback);
  }, delay);
};

Cùng ý tưởng này áp dụng cho API như addEventListener và Promise: Zone.js bọc lúc đăng ký callback, nên nó có quyền quan sát lúc callback chạy. Nó không đọc source code của bạn, cũng không biết mọi API trên đời; API nào không được patch hoặc callback nào được đăng ký ngoài Angular zone sẽ không tạo cùng notification.

Task, microtask và macrotask

Zone.js theo dõi các loại công việc mà event loop thực thi. Bạn chỉ cần phân biệt ba khái niệm sau để hiểu thời điểm Angular được báo:

LoạiVí dụTrực giác
Event taskclick, inputTrình duyệt gọi khi người dùng hoặc hệ thống tạo event.
MacrotasksetTimeout, setIntervalMột công việc độc lập ở lượt event loop sau.
MicrotaskPromise.then, phần tiếp theo của awaitChạy ngay sau code hiện tại, trước macrotask tiếp theo.

Ví dụ Promise tạo microtask:

Promise.resolve('Đã tải xong').then((message) => {
  this.message = message;
});

Sau callback, Zone.js theo dõi việc queue task của Angular zone có trở nên ổn định hay chưa. Khi phù hợp, Angular có thể lên lịch một lượt change detection. Bạn không cần tự đoán chính xác thứ tự microtask/macrotask khi viết component thông thường; điều cần nhớ là Zone.js có thể thấy các callback đã patch chạy xong.

Angular dùng Zone.js để chạy change detection

Luồng với timer hoặc Promise

Một component dùng field thường và setTimeout:

@Component({
  selector: 'app-status',
  template: `<p>{{ message }}</p>`,
})
export class StatusComponent implements OnInit {
  message = 'Đang chờ';

  ngOnInit(): void {
    setTimeout(() => {
      this.message = 'Đã tải xong';
    }, 1_000);
  }
}

Trong ứng dụng có Zone.js, luồng khái niệm là:

direction: right

n0: "Render <p>Đang chờ</p>" {
  width: 200
  height: 58
}

n1: "Timer callback chạy\ntrong Angular zone" {
  width: 176
  height: 58
}

n2: "Zone.js báo Angular\nAsync work hoàn thành" {
  width: 192
  height: 58
}

n3: "Kiểm tra view phù hợp" {
  width: 192
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
Render <p>Đang chờ</p>Timer callback chạytrong Angular zoneZone.js báo AngularAsync work hoàn thànhKiểm tra view phù hợp
Render <p>Đang chờ</p>Timer callback chạytrong Angular zoneZone.js báo AngularAsync work hoàn thànhKiểm tra view phù hợp
Timer trong Angular zone dẫn tới đồng bộ DOM (1/2)
direction: right

n3: "Kiểm tra view phù hợp" {
  width: 192
  height: 58
}

n4: "{{ message }} đổi\nDOM: Đã tải xong" {
  width: 160
  height: 58
}

n3 -> n4
Kiểm tra view phù hợp{{ message }} đổiDOM: Đã tải xong
Kiểm tra view phù hợp{{ message }} đổiDOM: Đã tải xong
Timer trong Angular zone dẫn tới đồng bộ DOM (2/2)

Change detection mới là bước đọc {{ message }} và sửa DOM. Zone.js chỉ tạo điều kiện để bước đó được chạy. Vì vậy, nói “Zone.js cập nhật UI” là cách nói tắt, không hoàn toàn chính xác.

Một event có render lại toàn bộ trang không?

Không. Với một (click) hoặc setTimeout, Zone.js có thể khiến Angular bắt đầu một lượt change detection; nó không xóa rồi dựng lại toàn bộ HTML, không tạo lại mọi component và không tự viết lại mọi DOM node.

Ở chiến lược mặc định (Default), Angular thường đi kiểm tra rộng từ root của ứng dụng để đánh giá các binding. Điều này có nghĩa nhiều expression template có thể được chạy lại. Nhưng ở mỗi binding, Angular so sánh giá trị cũ và mới; DOM chỉ được ghi khi giá trị thật sự khác. Nếu {{ user.name }} vẫn là An, text node tương ứng không bị ghi lại.

OnPush giúp thu hẹp lượt kiểm tra hơn nữa: Angular bỏ qua các component OnPush sạch, trừ khi chúng nhận input mới, có event trong subtree, AsyncPipe phát giá trị, được gọi markForCheck() hoặc signal mà template đã đọc thay đổi.

direction: right

n0: "Người dùng click" {
  width: 152
  height: 58
}

n1: "Zone.js / Angular\nTạo cơ hội change\ndetection" {
  width: 160
  height: 79
}

n2: "Kiểm tra view cần\nthiết\nDefault rộng, OnPush\nhẹp" {
  width: 184
  height: 100
}

n3: "Chỉ binding khác\nmới ghi vào DOM" {
  width: 152
  height: 58
}

n0 -> n1
n1 -> n2
n2 -> n3
Người dùng clickZone.js / AngularTạo cơ hội changedetectionKiểm tra view cầnthiếtDefault rộng, OnPushhẹpChỉ binding khácmới ghi vào DOM
Người dùng clickZone.js / AngularTạo cơ hội changedetectionKiểm tra view cầnthiếtDefault rộng, OnPushhẹpChỉ binding khácmới ghi vào DOM
Click mở cơ hội kiểm tra, không ghi lại mọi DOM

Vì thế, nên phân biệt: “Angular kiểm tra nhiều view” không đồng nghĩa với “trang bị render lại toàn bộ.” Signals cải thiện thêm việc biết view nào phụ thuộc vào state nào.

Zone.js không biết state nào đã đổi

Zone.js nhìn vào callback, không nhìn vào dependency của template. Hai callback sau đều có thể khiến Angular được đánh thức:

setTimeout(() => {
  this.message = 'Đã tải xong'; // Có thay đổi UI.
}, 1_000);

setTimeout(() => {
  console.log('Chỉ ghi log'); // Không thay đổi UI.
}, 1_000);

Zone.js không biết callback thứ hai không đụng đến UI. Nó chỉ biết “timer callback đã xong”. Angular dùng change detection và chiến lược như OnPush để giới hạn phần cần kiểm tra, nhưng notification ban đầu vẫn khá rộng.

Đó là một trong các động lực để Angular thêm Signals: signal cho framework biết state nào đổi và view nào đã đọc state đó. Xem Signals, Zone.js và Change Detection để học flow này từ góc nhìn state.

NgZone: ranh giới giữa Angular và code bên ngoài

NgZone là service Angular cung cấp để bạn chủ động chạy code trong hoặc ngoài Angular zone. Hầu hết component không cần dùng nó. Nó hữu ích khi bạn tích hợp thư viện có callback tần suất cao hoặc callback nằm ngoài Angular.

import { Component, NgZone, inject } from '@angular/core';

@Component({
  selector: 'app-example',
  template: `...`,
})
export class ExampleComponent {
  private readonly zone = inject(NgZone);
}

runOutsideAngular: tránh đánh thức Angular cho việc không liên quan UI

Dùng runOutsideAngular() cho công việc nhiều lần nhưng không làm thay đổi dữ liệu đang hiển thị: đo animation, xử lý dữ liệu tạm, hoặc lắng nghe event tần suất cao để tính toán nội bộ.

startAnimation(): void {
  this.zone.runOutsideAngular(() => {
    const frame = () => {
      updateCanvasDirectly();
      requestAnimationFrame(frame);
    };

    requestAnimationFrame(frame);
  });
}

Vì callback được đăng ký ngoài Angular zone, Angular không bị đánh thức sau từng frame chỉ vì animation canvas chạy. Đây là tối ưu có chủ đích; đừng đưa code ra ngoài zone nếu nó phải thay đổi template ở mọi callback.

run: quay lại Angular khi field thường cần cập nhật UI

Nếu callback đang ở ngoài Angular zone và bạn đổi field thường được template đọc, quay lại Angular bằng run():

status = 'Đang tải';

connect(uploader: { onDone(callback: (fileName: string) => void): void }): void {
  this.zone.runOutsideAngular(() => {
    uploader.onDone((fileName) => {
      this.zone.run(() => {
        this.status = `Đã tải: ${fileName}`;
      });
    });
  });
}

run() đưa callback trở lại Angular zone. Khi callback hoàn tất, Angular có cơ hội chạy change detection để {{ status }} cập nhật.

Với state UI hiện đại, một lựa chọn thường rõ ràng hơn là để status là signal và gọi status.set(...). Signal đã tạo notification cho template consumer; bạn không cần dùng NgZone.run() chỉ để làm signal update render được.

Các tình huống thường gặp

Thư viện gọi callback ngoài Angular zone

Dấu hiệu: log cho thấy field đã đổi, nhưng template không đổi. Điều này thường xảy ra khi thư viện khởi tạo callback ngoài Angular zone hoặc ứng dụng chạy zoneless.

Chọn một trong hai cách rõ ràng:

// Cách 1: Ứng dụng còn dùng Zone.js, field thường.
this.zone.run(() => {
  this.status = result;
});
// Cách 2: State UI là signal mà template đã đọc.
this.status.set(result);

Trong ứng dụng zoneless, ưu tiên cách thứ hai hoặc gọi ChangeDetectorRef.markForCheck() sau khi đổi field thường. Đừng kỳ vọng NgZone.run() một mình cung cấp semantics Zone.js khi Zone.js đã bị gỡ khỏi ứng dụng.

Sự kiện tần suất cao

scroll, mousemove, resize hoặc animation frame có thể chạy rất nhiều lần. Nếu mỗi callback đều khiến Angular kiểm tra giao diện, hiệu năng có thể giảm.

Trước khi dùng runOutsideAngular(), hãy xác định UI thật sự cần cập nhật bao nhiêu lần. Có thể throttle event, chỉ cập nhật signal khi giá trị hiển thị thực sự thay đổi, hoặc dùng API browser trực tiếp cho canvas. Tối ưu đúng là giảm notification thừa, không phải mặc định chạy mọi thứ ngoài Angular.

Signal trong ứng dụng còn dùng Zone.js

Signals không xung đột với Zone.js. Trong Angular v16–v20, nhiều ứng dụng dùng cả hai:

  • Zone.js tiếp tục hỗ trợ code cũ đổi field thường trong async callback.
  • Signal thông báo rõ ràng khi state được template đọc thay đổi.

Thêm Signals không tự động xóa zone.js khỏi bundle. Việc chuyển zoneless là một migration riêng, cần kiểm tra thư viện, forms, SSR và test.

Giới hạn và chi phí của Zone.js

Zone.js rất tiện cho mô hình Angular truyền thống, nhưng có các trade-off:

Trade-offVì sao xảy raHệ quả thực tế
Notification rộngZone.js thấy task hoàn tất, không thấy state dependencyAngular có thể bị đánh thức dù UI không đổi.
Patching toàn cụcNhiều browser API bị wrapper can thiệpStack trace và debugging có thể khó đọc hơn.
Tương thích APIAPI mới hoặc thư viện đặc biệt không phải lúc nào cũng được patch như mong muốnCó callback đổi state nhưng không tự cập nhật UI.
Kích thước/khởi độngZone.js là thêm một dependency runtimeCó thêm chi phí payload và startup.

Các trade-off này không có nghĩa Zone.js “xấu” hoặc dự án đang dùng nó là lỗi thời. Nó giải quyết tốt bài toán notification tổng quát. Angular hiện đại chuyển dần sang notification tường minh để việc cập nhật dễ dự đoán hơn.

Zone.js và zoneless

Zone.js chưa bị bỏ

Zoneless là mặc định cho ứng dụng mới từ Angular v21 trở lên, nhưng Zone.js không bị loại khỏi Angular. Dự án cũ đang nạp zone.js vẫn có thể tiếp tục hoạt động theo cơ chế zone-based; bạn không buộc phải migration chỉ vì nâng version Angular.

Nếu ứng dụng Angular v21+ cần chủ động dùng lại zone-based change detection, đăng ký provider sau và bảo đảm zone.js vẫn được nạp từ polyfills/cấu hình build:

import { provideZoneChangeDetection } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [provideZoneChangeDetection()],
});

Vì vậy, “zoneless là mặc định” chỉ có nghĩa Angular chọn notification tường minh cho app mới. Nó không có nghĩa NgZone, Zone.js hay các ứng dụng dựa trên Zone.js bị bỏ ngay lập tức.

Zoneless là chế độ Angular không nạp Zone.js để theo dõi mọi async task. Angular vẫn cần notification để chạy change detection, nhưng notification đến từ các API Angular rõ ràng hơn:

  • cập nhật signal mà template đã đọc;
  • event listener được bind trong template;
  • AsyncPipe nhận giá trị mới;
  • ChangeDetectorRef.markForCheck();
  • Angular gán input qua framework API.

Với Angular v20, có thể bật zoneless bằng provideZonelessChangeDetection(). Với dự án cũ, không tự gỡ zone.js khỏi polyfills mà chưa kiểm thử toàn bộ luồng async. Đọc hướng dẫn chính thức về zoneless trước khi migration.

NgZone không phải lý do để giữ Zone.js

NgZone.run() và NgZone.runOutsideAngular() vẫn có thể tồn tại trong code tương thích zoneless. Nhưng các observable ổn định như onStable, onUnstable hoặc onMicrotaskEmpty không còn là tín hiệu phù hợp để điều khiển render ở ứng dụng zoneless.

Checklist chẩn đoán

Khi field đã đổi nhưng UI không đổi trong dự án dùng Zone.js, kiểm tra theo thứ tự:

  1. Template có đọc đúng field/signal không? Đặt log hoặc breakpoint tại callback và kiểm tra tên binding.
  2. Callback có chạy trong Angular zone không? Tìm runOutsideAngular(), Web Worker và thư viện bên thứ ba.
  3. Component có OnPush không? Xác định notification hợp lệ: input mới, event, AsyncPipe, signal hoặc markForCheck().
  4. Object có bị mutate tại chỗ không? Với OnPush và signal, dùng reference mới khi cập nhật dữ liệu phức tạp.
  5. Ứng dụng có phải zoneless không? Nếu có, không dựa vào timer/Promise để tự đánh thức Angular; dùng notification tường minh.
  6. Có exception trước khi cập nhật không? Một lỗi runtime có thể khiến dòng gán hoặc set() không chạy.

Tóm tắt

  • Zone.js bọc nhiều API async để theo dõi callback và ngữ cảnh chạy của chúng.
  • Angular truyền thống dùng thông tin đó để biết khi nào có thể chạy change detection.
  • Zone.js không biết field, signal, component hoặc DOM node cụ thể nào đã đổi.
  • NgZone.runOutsideAngular() giảm notification thừa; NgZone.run() đưa callback về Angular zone khi field thường cần làm mới UI.
  • Zone.js vẫn được Angular hỗ trợ; zoneless là mặc định cho ứng dụng mới từ Angular v21, không phải việc loại bỏ Zone.js khỏi dự án cũ.
  • Signals cung cấp dependency theo state rõ ràng hơn; zoneless thay dần việc dựa vào patching toàn cục bằng notification tường minh.

Quay lại Signals, Zone.js và Change Detection để đặt Zone.js vào bức tranh đầy đủ từ state đến DOM.

On this page