I've noticed a strange behavior of change detection in Angular. When Observable updated as in the example, change detection not triggered for some reason.
The key here is setTimeout
called inside the callback, if you remove it, change detection will work fine. markForCheck
which inside AsyncPipe
also called as it should.
@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})`);
});
});
}
}
What happens:
The click triggers a CD cycle and marks the OnPush component to be checked. Timeout is started. Component is checked for changes, but the value did not change yet and so the UI is not updated. CD cycle is done.
Timeout elapses. It does not trigger another CD cycle, because it is inside
runOutsideAngular
. Value is nexted. Async pipe callsmarkForCheck
, but this does not have any visible effect until another CD cycle is triggered, e.g. by clicking the button another time.If
setTimeout
is removed, the value is nexted in the same CD cycle that has been triggered by the button click, and the UI will be updated.If
runOutsideAngular
is removed, the elapsing timeout triggers a new CD cycle, the component will be checked for changes because the async pipe calledmarkForCheck
, and the UI will be updated.