0%

angular-change-detection-markforcheck

Angular更新检测是自动执行的,但是有些情况下我们需要手动触发更新检测,Angular提供以下方法来手动触发更新检测:

  • detectChanges(): Forces an immediate check for the component and its children.
  • markForCheck(): Marks the component to be checked in the next cycle (useful with OnPush).
  • detach()/reattach(): Temporarily disables or re-enables change detection for a component.

When to use detectChanges

The follow cases are when you should use detectChanges:

1. When change detector is detached from current component.

1
2
3
4
5
6
7
8
ngDoCheck(): void {
if (this.currentAge! < 50) {
this.cdf.detach(); // stop change detection
this.cdf.markForCheck(); // not work, use detectChanges instead.
} else {
this.cdf.reattach(); // restore change detection
}
}

2. An update is happened, but its not in Angular zone, for example: 3rd party libraries.

1
2
3
4
myFunction(){
someFunctionThatIsRunByAThirdPartyCode();
this.cd.detectChanges();
}

Note that we can also fix this by wrapping the third party code in setTimeout or NgZone.run:

1
2
3
4
5
6
myFunction(){
setTimeout(() => {
someFunctionThatIsRunByAThirdPartyCode();
this.cd.detectChanges();
});
}
1
2
3
4
5
myFunction(){
this.ngZone.run(() => {
someFunctionThatIsRunByAThirdPartyCode();
});
}

Angular has monkey patched setTimeout, and will do the change detection after the setTimeout is finished.

3. There are also cases where you update the model after the change detection cycle is finished, where in those cases you get this dreaded error: "Expression has changed after it was checked";

When to use markForCheck

The most common case to use markForCheck is when your component use OnPush change detection strategy and you want to trigger change detection for the component and its ancestors.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core';
import 'reflect-metadata';
import {ProductDetailComponent} from "../product-detail/product-detail.component";
import {CardComponent} from "../card/card.component";
import {HoverDirective} from "../hover.directive";
import {Dir1Directive} from "../dir1.directive";
import {OrderService} from "../order.service";
import {Dir2Directive} from "../dir2.directive";
import {ProductItem} from "./product.model";

@Component({
selector: 'app-product',
standalone: true,
providers: [],
templateUrl: './product.component.html',
styleUrl: './product.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush, // OnPush mode
})
export class ProductComponent implements OnInit {
product: ProductItem | null = null;

constructor(private orderService: OrderService, private cdf: ChangeDetectorRef) {
}

ngOnInit(): void {
this.orderService.fetchData(1).then(product => {
this.product = product;
this.cdf.markForCheck(); // <--- must
})
}
}

Difference between detectChanges and markForCheck

  1. detectChanges triggers change detection for the component and its children.
  2. markForCheck marks the component and its ancestors for change detection, but it doesn’t trigger change detection immediately.
  3. detectChanges still work even when change detector is detached, but markForCheck doesn’t.

References

  1. https://angular.dev/api/core/ChangeDetectorRef#markForCheck