为什么runOutsideAngular回调中的setTimeout会跳过对可观察对象的变化检测,即使markForCh



我注意到Angular中一个奇怪的变化检测行为。当Observable像例子中那样更新时,由于某些原因没有触发变更检测。

这里的关键是回调内部调用的setTimeout,如果您删除它,更改检测将正常工作。markForCheck里面的AsyncPipe也应该叫

@Component({
selector: 'my-app',
template:
'<button (click)="click()">Trigger</button> <br> {{value$ | async}}',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
readonly value$ = new BehaviorSubject(1);
constructor(
private readonly zone: NgZone,
) {}
click() {
this.zone.runOutsideAngular(() => {
setTimeout(() => {
this.value$.next(this.value$.value + 1);
console.log(`Change (Should be ${this.value$.value})`);
});
});
}
}

StackBlits例子

StackBlitz与异步管道调试示例

结果:

点击触发一个CD循环,并标记OnPush组件要被检查。超时启动。检查组件的更改,但值尚未更改,因此UI未更新。CD循环完成

超时白白的流逝了。它不会触发另一个CD循环,因为它在runOutsideAngular内部。值是下一个。Async pipe调用markForCheck,但这不会有任何可见的效果,直到另一个CD周期被触发,例如,通过点击另一个按钮。

如果删除setTimeout,则该值将在下一个由点击按钮触发的CD周期中,并且UI将被更新。

如果runOutsideAngular被删除,超时触发一个新的CD循环,组件将被检查更改,因为异步管道调用markForCheck,并且UI将被更新。

最新更新