Side effects with BehaviorSubject and concatMap

439 Views Asked by At

This is my first time experimenting with BehaviorSubject, async pipes and concatMap so I'm facing some problems updating data in my DOM.

I have:

private profilesStore = new BehaviorSubject<any>(null);
profiles$ = this.profilesStore.asObservable();

  getUserProfiles(): Observable<Profile[]> {
    const headers = this.authService.getHeaders();
    return this.http.get<any>(`${API_URL}/profile`, { headers: headers })
      .pipe(
        catchError(err => throwError(err)),
        tap(res => this.profilesStore.next(res)),
        shareReplay()
      );
  }

and then

addProduct(profileId: any) {
    const headers = this.authService.getHeaders();
    return this.http.post<any>(`${apiUrl}/products/${profileId}`, {}, { headers: headers })
      .pipe(
        catchError(err => throwError(err)),
        concatMap(() => this.profileService.profiles$),
        map(profiles => {
          const selectedProfile = profiles.findIndex(profile => profile.id === profileId);
            profiles[selectedProfile].canEdit = true;
            return profiles;
        })
      );
  }

This logic it's like a cart logic. I add a product to one of the profiles, so to avoid calling again the api (getUserProfiles) I modify profiles$ stream and add the property that I want (in this case canEdit) but the problem comes when I delete the product from the cart and recover data from getUserProfiles() I understand that as I use concatMap with profiles$ I get the side effect on addProduct() even if I haven't called that function, my question is...

Why does it continue performing

map(profiles => {
          const selectedProfile = profiles.findIndex(profile => profile.id === profileId);
          profiles[selectedProfile].canEdit = true;
            return profiles;
        })

with the old profileId that i passed in the past as parameters, even if i haven't called addProduct() function and how to avoid that?

1

There are 1 best solutions below

1
On BEST ANSWER

Think of observables as a water tap that, once you subscribe to it, the tap is never closed. Water will keep flowing, as long as, well there is water. Only when you unsubscribe it (or any other operators that ends it), then the tap will close.

So the moment you do a addProfile(), even just once, the observable of it is forever opened, and as long as there is emissions of data (water), that emission of data will still continues, even if you dont call the function addProfile() anymore -> and that, if you call the function two times, you actually have two subscriptions aka two water pipes and taps; which is something that most developers did not pay attention to.

So when you call addProfile() just for the first time, and since you never unsubscribe it, you actually switched to listening to profiles$ thanks to

  concatMap(() => this.profileService.profiles$),

, your stream of data actually becomes listening to the changes of that BehaviourSubject, and that explains that even if you don't call addProfile() anymore, but because you update your profiles$, emissions still go through, and the flow of data will flow through the pipes, which, will then effectively perform the next pipe operator:

map(profiles => {
          const selectedProfile = profiles.findIndex(profile => profile.id === profileId);
          profiles[selectedProfile].canEdit = true;
            return profiles;
        })