Perform an action two different RxJS observers get 1 result

63 views Asked by At

I have two different observers, and upon geting the first result for each I perform an action:

Observer 1

specialties$.subscribe(
            (value) => {
                this.specialtiesOptions = value['result'].map((specialty) => {
                    return {...specialty, selected: false}
                })
            }
        )

Observer 2:

partner$.subscribe({
    next: (value: Partner) => {
        // other stuff happening here
        this.partner = mutate(value)
    }

I have another method, which is related to both, which should only be called when I get the value for both observers, and both this.specialties and this.partner are correctly populated.

How can I do this?

Is it possible to do this while keeping the operations logically separated.

2

There are 2 answers

0
Angulator1st On

I ended up doing it like this. Not sure if it's the cleanest or most idiomatic solution... but it works for me so far. Leaving in case it helps someone else (and so I can tell it sucks if it does!)

I create a couple Subjects:

const partnerIsDone$ = new Subject<boolean>()
const specialtiesAreDone$ = new Subject<boolean>()

which I update on each subscribe() call:

specialties$.subscribe(
            (value) => {
                this.specialtiesOptions = value['result'].map((specialty) => {
                    return {...specialty, selected: false}
                })
               specialtiesAreDone$.next(true)
            }
        )

partner$.subscribe({
    next: (value: Partner) => {
        // other stuff happening here
        this.partner = mutate(value)

        partnerIsDone$.next(true)
    }

And then subscribe to each Subject, and check if the properties are set:

const maybeApplySelectedSpecialties = () => {
                    if (this.specialtiesOptions && this.partner) {
                        this.selectPartnerSpecialties()
                    }
                };

partnerIsDone$.subscribe(() => maybeApplySelectedSpecialties());
specialtiesAreDone$.subscribe(() => maybeApplySelectedSpecialties());

It works. What I don't like is that I'm checking the status of the properties. In this specific case it works, but I'd prefer to check for example if both subjects are complete(), but not sure how would I do that.

2
olivarra1 On

The best way of dealing with observables is through chaining them.

It might seem like a burden at first when everything is wrapped on an Observable<Something> instead of having just Something, but on the long run it's great when you have to do asynchronous stuff.

What if this.specialtiesOptions wasn't a value, but an observable? You could define it as

this.specialitesOptions$ = specialties$.pipe(
  map(specialties =>
    specialties.result.map((specialty) => ({
      ...specialty,
      selected: false
    })
  )
)

Note how we haven't subscribed to anything yet. This just defines that specialtiesOptions$ is an observable which is going to do some computation over specialties$. You can do the same for partner:

this.partner$ = partner$.pipe(
  map(partner => mutate(partner))
)

And now when you need to do something, you can consume the observable and it will work:

combineLatest([
  this.specialtiesOptions$,
  this.partner$
]).subscribe((specialtiesOptions, partner) => {
  this.selectPartnerSpecialties(specialtiesOptions, partner)
})

The idea though is that you can keep chaining observables as you need them, and you .subscribe() as little as possible. This way of programming is harder though, you need a big mental shift from imperative to reactive, but for complex scenarios it pays off.

Things to keep in mind:

  • If you're expecting asynchronous values to just load once, you can convert them to promises (using firstValueFrom from RxJS) and it might make some things a bit simpler. But using observables allows you to react to changes, which is needed in many cases.
  • Use shareReplay(1) or share({ ...config }) when you don't want to repeat sending requests or recalculating every value. Observables are unicast, which means that every observer will have their own instance of the observable. share operators make them shared
  • You can make this.specialitesOptions$ as complex as you need... This version only loads the initial value and then it's done, but you could add in logic for when the values get changed (I guess based on user input, etc). If you do that, then any other observable depending on specialtiesOptions will receive the new value and recalculate whatever it needs to.