3

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.

4
  • 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. Commented Sep 14, 2022 at 10:10
  • getUserInfo: Subject<string> = new Subject<string>() that's the situation Commented Sep 14, 2022 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? Commented Sep 14, 2022 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). Commented Sep 14, 2022 at 11:51

1 Answer 1

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.

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

Comments

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.