Can someone explain that why the BehaviorSubject result like this?

Viewed 440

I wrote a service contain a BehaviorSubject called loginStatus and a function to return that.

private loginStatus = new BehaviorSubject<boolean>(false);

getLoginStatus(): Observable<boolean> {
  return this.loginStatus;
}

Then I subscribe it in other component's DOM with a async pipe in 4 different div, I just commented the shareReplay to show what I'm wondering.

  loginStatus$ = this.authService.getLoginStatus().pipe(
    switchMap(res => {
      if (!res) { return this.authService.isLoggedIn(); }
      else { return of(res); }
    }),
    tap(res => console.log('tap: ' + res)),
    //shareReplay(1),
 );

The isLoggedIn is a http request to check am I logged in or not, I check the loginStatus value in the switchMap, if it's false then call the isLoggedIn

isLoggedIn(): Observable<boolean> {
console.log('called isLoggedIn');
return this.httpService.get('/auth/isLoggined').pipe(
  map(res => {
    console.log('isLoggedIn res: ' + JSON.stringify(res));
    if (res) {
      this.loginStatus.next(true);
      this.firstName = res.first_name;
      this.lastName = res.last_name;
      this.tel = res.tel;
      return 'fool';
    } else {
      this.clearAll();
      return false;
    }
  }),

Finally It's my console result:

called isLoggedIn
called isLoggedIn
called isLoggedIn
called isLoggedIn

isLoggedIn called 4 times because of my 4 async pipe, and the loginStatus is false.

while the first http request response, I got a json object like {first_name:xxx, last_name:yyy, 1234}, so I push the true to the loginStatus and return a 'fool' to the further operator.

I suppose that I will get the fool in the tap operator of the loginStatus$, but it doesn't.

isLogedIn res: {"logined":true,"first_name":"xxx","last_name":"yyy","tel":"1234"}
tap: true
tap: true
tap: true
tap: true

I only got the response of the first isLoggedIn request, the other 3 is lost. And I never get the fool, all the async pipe get the true from the loginStatus BehaviorSubject I guess.

Then I commented the this.loginStatus.next(true) in the isLoggedIn function to compare the result.

isLogedIn res: {"logined":true,"first_name":"xxx","last_name":"yyy","tel":"1234"}
tap: fool
isLogedIn res: {"logined":true,"first_name":"xxx","last_name":"yyy","tel":"1234"}
tap: fool
isLogedIn res: {"logined":true,"first_name":"xxx","last_name":"yyy","tel":"1234"}
tap: fool
isLogedIn res: {"logined":true,"first_name":"xxx","last_name":"yyy","tel":"1234"}
tap: fool

It's result with this which is what I prefered, I called 4 times, so I got 4 responses and It pass the fool to the tap 4 times.

I wanna know why the other reponse is lost when I change the BehaviorSubject in the process of the first subscription.
And why all 4 subscription get the true value, I suppose at least the first time will get the fool.
Does it means the BehaviorSubject will cancel the further process when change rather than complete it?

2 Answers

That happened because you're using switchMap, which will cancel the previous inner observable and handle the new one.

So when you emitting a value within the BehaviorSubject using this.loginStatus.next(true), the switchMap operator will cancel the previous observable and handle the new value (which is true).

If you want to achieve your scenario you should keep the previous inner observable completed normally, then you need to use either mergeMap or concatMap operators.

Please try this

       private loginStatus = new BehaviorSubject<boolean>(false);
        
        getLoginStatus(): Observable<boolean> {
          return this.loginStatus.asObservable();
        }
    
        this.loginStatus$ = this.getLoginStatus().pipe(
          mergeMap((res) => {
            if (!res) {
              return this.isLoggedIn();
            } else {
              return of(res);
            }
           }),
           tap(res => console.log('tap: ' + res)),
         );
      }

This is giving everything 4 results as expected.

This is happening because of switchMap operator.

when ever you call this.loginStatus.next(true); it is switching to latest event and from this.loginStatus$ observable and inside else statment executes and in tap true is printed times.

If you set this.loginStatus.next(false); this will set to an infinite loop.

Conclusion

if you put any number of loginStatus$ | async because you are using switchMap, the inner observable will be called once.

The tap pipe after the switchMap will be called according to number of | async pipe subscriptions.

Related