Итак, я пытаюсь лучше понять Angulars ChangeDetection и наткнулся на проблему: https://plnkr.co/edit/M8d6FhmDhGWIvSWNVpPm?p=preview
Этот Plunkr представляет собой упрощенную версию кода моего приложения и в основном имеет родительский и дочерний компоненты. У обоих ChangeDetectionStrategy.OnPush
включен.
parent.component.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();
}
}
child.component.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);
}
}
Таким образом, родитель обновляет data
при нажатии кнопки, которая запускает ngOnChanges
в дочернем элементе. Каждый раз при изменении данных child.component
изменяет значение в stats
. Я хочу, чтобы это значение dataSize
использовалось в <span *ngIf="stats.dataSize > 0">Works</span>
в родительском элементе. По какой-то причине *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();
в родительском в handleStatsChange
. handleStatsChange
будет вызываться в дочернем. Это не имеет никаких последствий, в любом случае создается исключение.
Я предполагаю, что обнаружение изменений в родительском элементе не запускается, потому что в родительском элементе @Input не изменилось, поэтому ngIf не обновляется ?? Нажав кнопку два раза, вы увидите Works
. I это потому, что теперь запускается новый цикл дайджеста (запускается событием) и родители ChangeDetectorRef теперь обновляют шаблон?
Так почему же Angular обновляет {{stats.dataSize > 0}}
и выдает ошибку в ngIf?
Любая помощь очень ценится :)