0

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})`);
      });

    });
  }
}

StackBlits example

StackBlitz with async pipe debug example

4
  • Why are you doing that though Commented Jan 5, 2023 at 13:47
  • 1
    "Why does setTimeout inside runOutsideAngular callback skip change detection for the observable", that's the idea of putting logic into runOutsideOfAngular... there is no change detection in there, is outside of zone.js... after you do whatever logic in there, in order to add it to the zone, you need to add this.zone.run() Commented Jan 5, 2023 at 14:53
  • Why would you add logic into runOutsideAngular if you need change detection in there.. for that, just do your logic without NgZone? Commented Jan 5, 2023 at 14:57
  • Because one of our external libraries uses runOutsideAngular (I don't know why) and potentially puts some observable mutation at the end of the event loop. As a result, this leads to the behavior that I described in the example. Commented Jan 6, 2023 at 16:00

1 Answer 1

1

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 calls markForCheck, 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 called markForCheck, and the UI will be updated.

Sign up to request clarification or add additional context in comments.

1 Comment

Refreshed my memory on markForCheck, thank you very much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.