所以我试图更好地理解 Angulars ChangeDetection 并无意中遇到了一个问题: https://plnkr.co/edit/M8d6FhmDhGWIvSWNVpPm?p=preview
这个 Plunkr 是我的应用程序代码的简化版本,基本上有一个parent 和一个child 组件。
两者都有 ChangeDetectionStrategy.OnPush
启用。
父组件.ts
@Component({
selector: 'parent',
template: `
<button (click)="click()">Load data</button>
{{stats.dataSize > 0}}
<span *ngIf="stats.dataSize > 0">Works</span>
<child [data]="data" [stats]="stats" (stats)="handleStatsChange()"></child>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent implements OnCheck, OnChanges {
data = [];
stats = {
dataSize: 0
};
constructor(private cdr: ChangeDetectorRef) {
}
click() {
console.log("parent: loading data");
setTimeout(() => {
this.data = ["Data1", "Data2"];
this.cdr.markForCheck();
});
}
handleStatsChange() {
console.log('parent: stats change');
this.cdr.markForCheck();
}
}
子组件.ts
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from "@angular/core";
@Component({
selector: 'child',
template: `
<div *ngFor="let item of data">{{item}}</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit, OnChanges {
@Input() data;
@Input() stats;
@Output('stats') statsEmitter = new EventEmitter();
constructor() {
}
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
console.log("child changes: ", changes);
this.stats.dataSize = changes['data'].currentValue.length;
this.statsEmitter.emit(this.stats);
}
}
所以 parent 更新data
单击按钮触发 ngOnChanges
在 child 。
每次数据更改时,child.component
更改 stats
中的值.
我想要这个值,dataSize
, 用于 <span *ngIf="stats.dataSize > 0">Works</span>
在 parent 。
出于某种原因 *ngIf
不会更新。模板{{stats.dataSize > 0}}
否则更新没问题。
我注意到:
如果我删除 OnPush
在父级上,Angular 将抛出异常 Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
.
我想这来自 *ngIf="stats.dataSize > 0"
在开发模式下进行第二次变更检测迭代后,首先为假,现在为真。
这就是我尝试设置 this.cdr.markForCheck();
的原因在 parent 中 handleStatsChange
. handleStatsChange
将被称为 child 。这没有任何后果,无论如何都会抛出异常。
我猜父项上的变化检测不会被触发,因为父项中没有更改@Input,因此 ngIf 不会更新?点击按钮两次实际上会显示Works
.我这是因为新的摘要周期现在开始(由事件触发)并且 parent ChangeDetectorRef 现在正在更新模板?
那么为什么 Angular 会更新 {{stats.dataSize > 0}}
并在 ngIf 处抛出错误?
非常感谢任何帮助:)
最佳答案
我今天遇到 intersection observer 时遇到这个问题,其中将项目滚动到 View 中应该会执行某些操作,并且 99% 的时间它会执行,但 1% 的时间它不会触发。
重要:异步管道实际上在内部调用了markForCheck
。你可以看看the source code (接近尾声)。
我过去使用可观察对象解决此问题的一个解决方案是创建一个创建“延迟”的管道。这有助于解决“表达式已更改”错误,因为它有效地导致了新的变化检测器循环。
这应该是最后的手段 - 但我已经用过几次,因为我无法在其他地方获得正确的时机。
<div *ngIf="model.showPanel | delayTime | async">...</div>
默认值为 0ms,delay()
RxJS 管道使用 setTimeout:
@Pipe({
name: 'delayTime'
})
export class DelayTimePipe implements PipeTransform {
constructor() {}
transform(value?: Observable<any> | any, ms?: number): Observable<any> {
if (isObservable(value)) {
return value.pipe(delay(ms || 0));
} else {
throw debuggerError('[DelayTimePipe] Needs to be an observable');
}
}
}
关于 Angular 变化检测 NgIf,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42861377/