1

In Angular (v12) I've got the component you can see below:
component.html

<h3 *ngIf="userInfo$ | async as userInfo; else loading">{{ userInfo.user.name }}</h3>

<ng-template #loading>
    Loading...
</ng-template>

component.ts

token: string;
userInfo$: Observable<any>
getUserInfo: Subject<string> = new Subject<string>()

constructor(
  private service: Service,
  private route: ActivatedRoute
) { }

ngOnInit(): void {
  this.userInfo$ = this.getUserInfo.pipe(
    mergeMap(token => {
      return this.service.getKey(token).pipe(
        map(res => {
          return {
            user: res.client,
            info: res.info
          }
        })
      );
    }),
    tap((res) => console.log(res))
  )

  this.route.paramMap.subscribe(params => {
    this.token = params.get("token");
    this.getUserInfo.next(this.token);
  });
}

Using the async pipe the user'll get a perpetual loading, while if I this.getUserInfo.pipe(..).subscribe() the right response is logged.
I know that the async pipe subscribes and unsubscribes to the observables, so I expected the ngIf to be truthful.

Igor Cantele
  • 387
  • 1
  • 10
  • 2
    What type does the `getUserInfo` have? I suspect it is a plain `Subject`. I suggest you to convert it to a `BehaviorSubject` instead. This way, the last emitted value will also be "given" to the new subscribers, even if they subscribe after the `emit`. – Octavian Mărculescu Sep 14 '22 at 10:10
  • `getUserInfo: Subject = new Subject()` that's the situation – Eli Porush Sep 14 '22 at 11:30
  • @OctavianMărculescu It was indeed my problem, I edited my question in order to make it clearer for future readers. If I've correctly understood `Subject` need to be subscribed before passing a value with `next()`, so my problem was that I was passing the value `onInit`, thus the `async` pipe didn't have enough time to subscribe. Is that correct? – Igor Cantele Sep 14 '22 at 11:48
  • I don't know in great detail the whole chronology of what is happening, but looking at the behavior, it seemed to me like this is the problem. Your subject emitted before the async pipe subscribed to it. In this case, it is better for you to use `BehaviorSubject` or `ReplaySubject` with a buffer size of 1 (for cases where a default value does not make sense). – Octavian Mărculescu Sep 14 '22 at 11:51

1 Answers1

1

You have an issue with timing. The problem is that this.getUserInfo.next(this.token) emits before the async pipe subscribes, so you don't receive the emission.

You can simplify your observable chain a bit which would side-step this timing issue.

@Component(...)
class MyComponent() {

    private token$ = this.route.paramMap.pipe(
        map(params => params.get('token')),
        distinctUntilChanged()
    );

    userInfo$ = this.token$.pipe(
        switchMap(token => this.service.getKey(token)),
        map(res => ({
            user: res.client,
            info: res.info
        }))
    );

    constructor(
        private service: Service,
        private route: ActivatedRoute
    ) { }

}

Notice the token is defined as an observable and we defined userInfo$ as an observable that depends on the token$ emissions. There's no need for a separate Subject and a subscription. We also don't need to use ngOnInit.

BizzyBob
  • 12,309
  • 4
  • 27
  • 51