Warm tip: This article is reproduced from serverfault.com, please click

Conditionally execute the switchMap, otherwise, cancel all without firing the error event

发布于 2020-12-01 14:22:11

I'm doing a web application in Angular 10. I try to keep working with Observables instead of Promises, but sometimes is hard to build a flow of executions.

I want to accomplish this flow:

  1. Present a confirmation modal
  2. If the confirmation has a value ({isConfirmed: true}), present a loading (here is the condition).
  3. Do a switchMap to start the execution of HTTP calls.
  4. If any HTTP call fails, hide the loading and show a warning message

Logic:

  send(title: string, message): Observable<any> {
    const isConfirmed: Promise<{isConfirmed: boolean}> = this.alertService.warningConfirmation(
      'Send message',
      'Are you sure you want to send the message?',
      'Send',
      'Cancel'
    );

    return from(isConfirmed)
      .pipe(
        tap(res => res.isConfirmed ? this.loadingService.present() : EMPTY),
        switchMap(res => from(this.service.httpCallOneGetUsers())
          .pipe(
            map(users => users.map(user => user.email)),
            switchMap(emails => this.service.httpCallTwoSendMessage(title, message, emails))
          )
        ),
        finalize(() => this.loadingService.dismiss()),
        tap({
          next: () => console.log('Message sent!'),
          error: () => console.log('Could not send the message.')
        })
      );
  }

When I cancel the confirmation, the error event is fired. Also, the loading is never shown.

I have 2 HTTP calls inside the first switchMap and both may fail, one returns a Promise and the other returns an Observable. That's why I have added the first HTTP call inside of from().

My goal is:

  1. Conditionally execute/enter the switchMap if the confirmation result is isConfirmed: true. Otherwise, cancel all and get out of the logic without firing the error event.
  2. Where is the perfect location to add the tap or finalize to hide the loading and show a success/error message to the user.
Questioner
RRGT19
Viewed
0
adrisons 2020-12-02 17:28:07

1

Conditionally execute/enter the switchMap if the confirmation result is isConfirmed: true.

Try filter. E.g:

from(isConfirmed).pipe(
  filter((res) => !!res.isConfirmed)
  ...
)

This will only emit values that match the filter condition.

2

Where is the perfect location to add the tap or finalize...?

  • finalize: At the end, so if any subscription fails before it, it is executed.

  • tap: Wherever you want something to be executed. For example, if you wanted to print users o emails you'd need to put the tap instruction in the part of the pipe where you have that data.


Summarizing:

from(isConfirmed).pipe(
  filter((res) => !!res.isConfirmed),
  switchMap((res) => from(this.service.httpCallOneGetUsers())),
  map((users) => users.map((user) => user.email)),
  switchMap((emails) =>
    this.service.httpCallTwoSendMessage(title, message, emails)
  ),
  finalize(() => this.loadingService.dismiss()),
  tap({
    next: () => console.log("Message sent!"),
    error: () => console.log("Could not send the message."),
  })
);

Note: the last tap acts like the receiver of the subscription result, thats why it is last. But that code should go in the subscribe.

UPDATE (from comments)

To finalize the observable when the condition is not met you should do something like this:

from(isConfirmed).pipe(
  switchMap((res) => {
    if (res.isConfirmed) {
      this.loadingService.present();
      return from(this.service.httpCallOneGetUsers());
    } else {
      throwError("");
    }
  }),
  map((users) => users.map((user) => user.email)),
  switchMap((emails) =>
    this.service.httpCallTwoSendMessage(title, message, emails)
  ),
  finalize(() => this.loadingService.dismiss()),
  tap({
    next: () => console.log("Message sent!"),
    error: () => console.log("Could not send the message."),
  })
);

If the condition is true, the pipe will execute in order as expected. If the condition is false, from the first switchMap it will jump to finalize.