4.3 Change detection - quan1997ap/angular-app-note GitHub Wiki
Tài liệu
angular-change-detection-demo.zip.txt
Docs
Change detection là một cơ chế theo dõi sự thay đổi, cho phép nội dung ở phần giao diện luôn được đồng bộ với sự thay đổi trong model tương ứng
1. Khi nào App chạy change detection ?
- DOM events (mouse, key, …)
- Requests api
- Timers (setTimers(), setInterval())
- gọi method detectChanges() bằng đối tượng ChangeDetectorRef
- Gọi phương thức ApplicationRef.tick()
- @input thay đổi
- khi có 1 sự kiện DOM, @output
- Khi async pipe emit data
- Dùng phương thức markForCheck
- Chạy code trong ngZone.run()
2. Khi đặt ChangeDetectionStrategy.OnPush thì các event nào không kích hoạt thay đổi ?
- Data đầu vào @Input chỉ thay đổi giá trị mà không phải thay đổi tham chiếu
- setTimeout
- setInterval
- Promise.resolve().then(), Promise.reject().then()
- Requests api
3. Khi đặt ChangeDetectionStrategy.OnPush thì các event nào chạy kích hoạt thay đổi ?
- Kích hoạt lại bằng cách thủ công: gọi method detectChanges()
- Gọi phương thức ApplicationRef.tick()
- Tham chiếu đầu vào @input thay đổi
- Chính component này hoặc 1 trong các component con của nó kích hoạt 1 sự kiện
- Khi async pipe emit data
- Dùng phương thức markForCheck
4.Nên đặt ChangeDetection OnPush ở đâu ?
- Ở các component được định nghĩa trong router: thường các component này là 1 màn hình, data của nó do nó tự call về chứ không phụ thuộc vào thằng khác, nên không có lí do gì để nó chạy ChangeDetection, ngoài ra khi đặt OnPush ở đây thì các component con của nó cũng không chạy ChangeDetection, bởi vì các component con của nó hầu như chỉ phụ thuộc vào data từ component này
- Component là 1 màn hình hoặc component không phụ thuộc data bên ngoài nó
- Đặt OnPush trong component có chứa nhiều cấp hoặc chứa nhiều component con và đặt trong các component con của nó
5.Cẩn thận
- Khi dùng OnPush mà lại thay đổi data ở ngAfterViewInit thì sẽ có lỗi ExpressionChangedAfterCheckedError, data mới nhất sẽ không được render, lỗi này chỉ sảy ra ở develop mode nên cần bật console để check
- Phải control được khi nào cần chạy changeDetection để kích hoạt lại, nếu không có thể xảy ra lỗi do data mới không được cập nhật
Overview
Một ứng dụng Angular được mô tả là một cây components. Nếu có một cây component như sau A -> B -> C thì thứ tự các lời gọi hook và cập nhật binding như sau:
A: AfterContentInit
A: AfterContentChecked
A: Update bindings
B: AfterContentInit
B: AfterContentChecked
B: Update bindings
C: AfterContentInit
C: AfterContentChecked
C: Update bindings
C: AfterViewInit
C: AfterViewChecked
B: AfterViewInit
B: AfterViewChecked
A: AfterViewInit
A: AfterViewChecked

Change Detection Strategy
Có 2 “chiến lược” phát hiện thay đổi
-
Default: là CheckAlways, hay là “luôn luôn lắng nghe, luôn luôn thấu hiểu”. Change Detection là tự động dựa vào zone.js như đã nói ở trên.
-
OnPush: là checkOnce, tức “chỉ một lần này thôi nhé”. Nó chỉ chạy Change Detection lúc đầu tiên khi component khởi tạo. Sau đó chỉ có 2 trường hợp là được trigger Change Detection: một là thay đổi của Input của component (về giá trị nếu là immutable, hoặc reference nếu là mutable, aka, object), hai là từ event của template như onClick, onMousemove…
Chú ý
- hook OnPush chỉ được gọi trên component cao nhất của cây component trên nhánh đã bị vô hiệu, không phải trên mọi component của nhánh này.
- Khi một component được set ở chế độ OnPush, thì tất cả children của nó cũng được set OnPush và không thể override ở component con thành Default được.
1. detach : Vô hiệu việc kiểm tra trên view hiện tại:
export class AComponent {
constructor(public cd: ChangeDetectorRef) {
this.cd.detach();
}
}
Kết quả: Hệ quả là phần bên trái (màu cam) của cây component sẽ không được kiểm tra:

2. reattach : Enable việc kiểm tra trên view hiện tại:
VD: Khi chúng ta nhận thấy sự thay đổi của thuộc tính input, chúng ta có thể kích hoạt Change detection đối với component hiện tại và loại bỏ Change detection ở lần tiếp theo
export class AComponent {
@Input() inputAProp;
constructor(public cd: ChangeDetectorRef) {
this.cd.detach();
}
ngOnChanges(values) {
this.cd.reattach();
setTimeout(() => {
this.cd.detach();
})
}
Chú ý:
- Hàm reattach chỉ cho phép kiểm tra trên component hiện tại.
3. markForCheck
Hàm markForCheck cho phép kiểm tra trên tất cả components cha lên tới root component. Điều này có nghĩa rằng hàm reattach chỉ hữu dụng trên component cao nhất của nhánh đã bị vô hiệu.
Component({
...,
changeDetection: ChangeDetectionStrategy.OnPush
})
MyComponent {
@Input() items;
prevLength;
constructor(cd: ChangeDetectorRef) {}
ngOnInit() {
this.prevLength = this.items.length;
}
ngDoCheck() {
if (this.items.length !== this.prevLength) {
this.cd.markForCheck();
this.prevLenght = this.items.length;
}
}
4. detectChanges
Hàm này thực hiện theo dõi thay đổi một lần trên component hiện tại và tất cả thành phần con của nó, bất kể trạng thái của component là gì. Việc kiểm tra component và view hiện tại có bị vô hiệu hay không cũng sẽ bị bỏ qua.
export class AComponent {
@Input() inputAProp;
constructor(public cd: ChangeDetectorRef) {
this.cd.detach();
}
ngOnChanges(values) {
this.cd.detectChanges();
}
Kết quả: DOM được cập nhật khi có sự thay đổi thuộc tính ngay cả khi tham chiếu change dectector đã bị loại bỏ (detached).