Sven Laschinski
Sven Laschinski

Reputation: 23

Is there an easier, more elegant solution for Observables within RxJS pipes?

Imagine I have an observable giving me chocolate cookies, but I don't want to eat white ones. But since I'm blind I have to hand them over to a service to find out if a given cookie is white or not. But I don't get the answer right away. I'd rather get another observable.

So here is the code I came up with, but I really don't like it and I think there should be a much easier and more elegant solution to this:

// pipe the observable
chocolateCookieObservable$.pipe(
  // use a switchMap to create a new stream containing combineLatest which combines...
  switchMap((chocolateCookie: ChocolateCookie) => combineLatest(
    // an artificially created stream with exactly one cookie...
    of(chocolateCookie),
    // and the answer (also an observable) of my cookie service
    this.chocolateCookieService
      .isChocolateCookieWithWhiteChocolate(chocolateCookie),
    // so I get an observable to an array containing those two things
  )),
  // filtering the resulting observable array by the information contained in 
  // the array (is the cookie white)?
  filter(([chocolateCookie, isWhite]: [ChocolateCookie, boolean]) => !isWhite),
  // mapping the array so that I can throw away the boolean again, ending up 
  // with only the now filtered cookies and nothing more
  map(([chocolateCookie]: [ChocolateCookie, boolean]) => chocolateCookie),
).subscribe((chocolateCookie: ChocolateCookie) => {
  this.eat(chocolateCookie);
}

While this does work and is somewhat reasonable, it really gets extremely confusing if you have to encapsulate more of those within each other. Isn't there any way to directly filter the observable or mapping it so I get the information I need without using that strange combineLatest-of combination?

Upvotes: 2

Views: 103

Answers (2)

kos
kos

Reputation: 5364

You could use filter inside of the switchMap to filter out white cookies, and then map response from the service back to the cookie

Heres an example:

chocolateCookieObservable$.pipe(
  switchMap((chocolateCookie: ChocolateCookie) =>
    // async test if its white
    this.chocolateCookieService
      .isChocolateCookieWithWhiteChocolate(chocolateCookie)
      .pipe(
        // filter out white cookies
        filter(isWhite => !isWhite),
        // map back from isWhite to the cookie
        mapTo(chocolateCookie)
      )
  )
).subscribe((chocolateCookie: ChocolateCookie) => {
  // mmm, cookies!!!
  this.eat(chocolateCookie);
})

Upvotes: 1

Will Taylor
Will Taylor

Reputation: 1759

You should break this down into multiple statements.

Working this way will allow you to produce more readable, maintainable code when implementing complex async workflows with Observables.

When refactored, your code would look something like this:

const isWhite$ = chocolateCookie$.pipe(
    switchMap((chocolateCookie: ChocolateCookie) => this.chocolateCookieService.isChocolateCookieWithWhiteChocolate(chocolateCookie)),
);

chocolateCookie$.pipe(
    withLatestFrom(isWhite$),
    filter(([chocolateCookie, isWhite]: [ChocolateCookie, boolean]) => !isWhite),
    map(([chocolateCookie]: [ChocolateCookie, boolean]) => chocolateCookie),
  ).subscribe((chocolateCookie: ChocolateCookie) => {
    this.eat(chocolateCookie);
  }

Also, note that you don't really need to append 'Observable' to the end of the variable name as you are already using the $ syntax to denote that the variable is an Observable.

Upvotes: 1

Related Questions