#RxJS - CombineLatest/ForkJoin but with dependencies

19 messages · Page 1 of 1 (latest)

gusty dome
#

Hello everyone!

I might have entered a nasty knot of observables and I don't know what is the best option to proceed with.

I have three requests:

  • getUser, which returns a user.
  • verifyEmail, which needs the user's email.
  • sendEmail, which sends the user an email.

In the middle of all of it, I need to do something with the user itself.

I thought about the following:

this.userService.getUser().pipe(
  switchMap(user => combineLatest([of(user], this.userService.verify(user.email)),
).subscribe(([user, emailResult]) => {
  // Use the user.
  setProfile(user);
  this.router.navigate(['home']);
  // Need to send an email.
  this.userService.sendEmail(user).subscribe((email) => console.log('example done'));
})

But the second subscribe there feels very weird and I think it could be included in the pipe. But how?

I'm struggling because the 2nd and 3rd sources depend on the first one.

Extra question: What if the second request fails? I dont need to call the third if it does.

novel heart
#

There are quite a few oddities in there

#
this.userService.getUser().pipe(
  // with the user, verify the email
  switchMap(user => this.userService.verify(user.email).pipe(
    // then combine the values
    // (even though emailResult is never used later)
    map(emailResult => ({ user, emailResult }))
  )),
  tap(({ user, emailResult }) => {
    // this could just use userService.getUser()
    setProfile(user);
    // not sure why this is in the middle of everything
    this.router.navigate(['home'])
  }),
  switchMap(({ user }) => this.userService.sendEmail(user))
).subscribe(email => console.log('example done'));
#

Also switchMap may not be the best choice in there but that's another story

#

combineLatest is meant to tell you when either of the observed things change, and here you're giving it stuff that will not change (since you use of() and I assume verify has only one value)

gusty dome
#

I understand, nice comment on the combineLatest!

#

Indeed, as this is just a dependency cascade, I thought it would help, but not really.

#

Mapping the result as an object would work great, actually.

novel heart
#

Hm true made it an object, not a tuple

#

If verify() does a throwError() when it fails and returns EMPTY otherwise

#

You could do something like

#
this.userService.getUser().pipe(
  mergeMap(user => concat(
    this.userService.verify(user.email),
    this.userService.sendEmail(user)
  ))
);
gusty dome
#

Yeah that sounds reasonable!

gusty dome
novel heart
#

Yes, the error would "propagate"

#

If you want to prevent that, you can can .pipe(catchError())

#

e.g.

switchMap(user => this.userService.verify(user.email).pipe(
  map(emailResult => ({ user, emailResult })),
  catchError(_err => ({ user, emailResult: 'failed' }))
)),
#

But if you do

#
switchMap(user => this.userService.verify(user.email).pipe(
  map(emailResult => ({ user, emailResult }))
)),
catchError(_err => ({ user, emailResult: 'failed' }))

then you're not in the "inner" pipe anymore so the whole pipe will be error'ed (and you can't recover from that)