So i am trying to better understand Angulars ChangeDetection and stumbled into a problem: https://plnkr.co/edit/M8d6FhmDhGWIvSWNVpPm?p=preview
This Plunkr is a simplified version of my applications code and basically has a parent and a child component.
Both having ChangeDetectionStrategy.OnPush
enabled.
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);
}
}
So parent updates data
on button click which triggers ngOnChanges
in child.
Everytime data changes, child.component
changes a value in stats
.
I want this value, dataSize
, to be used in the <span *ngIf="stats.dataSize > 0">Works</span>
in parent.
For some reason the *ngIf
wont be updated. The template {{stats.dataSize > 0}}
otherwise updates no problem.
What i noticed:
If i remove OnPush
on parent, Angular will throw a exception Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
.
I guess this comes from *ngIf="stats.dataSize > 0"
being false first and now true after the second iteration of change detection in dev mode.
So thats why i tried setting this.cdr.markForCheck();
in parent in handleStatsChange
. handleStatsChange
will be called in child. This has no consequences thought, exception thrown anyway.
I guess change detection on parent doesnt get triggered because no @Input changed in parent, therefore ngIf doesnt update?? Clicking the button two times will actually show Works
. I this because a new digest cycle does now start (triggered by a Event) and parents ChangeDetectorRef is now updating the template?
So why does Angular update {{stats.dataSize > 0}}
and throw an error at ngIf?
Any help much appreciated :)