angularasync-pipe

Why is not Subject.next () emission detected in Angular template with async pipe?


There is following code and I am wondering why Subject.next(1) emmision is not visible in template whereas BehaviorSubject.next() emission is visible.

I know that wrapping it inside setTimeout() fixes it, but I would like to understand it.

I seems, like Subject.next() is synchronnous and async pipe does not receive / mark for check.

@Component({
  selector: 'my-app',
  template: `
    <div>Subj: {{ subj$ | async }}</div>
    <div>BehavSubj: {{ behavSubj$ | async }} </div>
    <div>Interval: {{ interval$ | async }} </div>
  `,
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  subj$ = new Subject<number>();
  behavSubj$ = new BehaviorSubject<number>(1);
  interval$ = interval(1000).pipe(take(100));

  ngOnInit(): void {
    this.subj$.next(1);
    this.behavSubj$.next(2);
  }
}

https://stackblitz.com/edit/angular-ivy-crnfgc?file=src/app/app.component.ts


Solution

  • It's not detected because it's to early. Replace all occurrences of OnInit with AfterViewInit and it works.

    import { Component, AfterViewInit, OnInit, VERSION } from "@angular/core";
    import { BehaviorSubject, interval, Subject } from "rxjs";
    import { take } from "rxjs/operators";
    
    @Component({
      selector: "my-app",
      template: `
        <div>Subj: {{ subj$ | async }}</div>
        <div>BehavSubj: {{ behavSubj$ | async }}</div>
        <div>Subj: {{ subj2$ | async }}</div>
        <div>BehavSubj: {{ behavSubj2$ | async }}</div>
        <div>Interval: {{ interval$ | async }}</div>
      `,
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent implements OnInit, AfterViewInit {
      subj$ = new Subject<number>();
      behavSubj$ = new BehaviorSubject<number>(1);
      subj2$ = new Subject<number>();
      behavSubj2$ = new BehaviorSubject<number>(1);
      interval$ = interval(1000).pipe(take(100));
    
      ngOnInit(): void {
        this.subj$.next(1);
        this.behavSubj$.next(2);
      }
    
      ngAfterViewInit(): void {
        this.subj2$.next(1);
        this.behavSubj2$.next(2);
      }
    }
    
    

    First you have to init the view so that the async pipe is already subscribed. Then you can send a value to a simple subject. A behavior subject is different. It stores its last value. You can first update a behavior subject and then subscribe. That's also the reason why you have to initialize a behavior subject but it makes no sense to initialize a basic subject.

    Here is an example.