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:
{isConfirmed: true}
), present a loading (here is the condition).switchMap
to start the execution of HTTP calls.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:
switchMap
if the confirmation result is isConfirmed: true
. Otherwise, cancel all and get out of the logic without firing the error event.tap
or finalize
to hide the loading and show a success/error message to the user.Conditionally execute/enter the
switchMap
if the confirmation result isisConfirmed: true
.
Try filter
. E.g:
from(isConfirmed).pipe(
filter((res) => !!res.isConfirmed)
...
)
This will only emit values that match the filter condition.
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
.
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
.
Where to add the code of presenting the loading? Maybe after the
filter
using anotherswitchMap
? Also, I don't understand thefilter
, you are saying that if its value istrue
the flow will continue, if it'sfalse
then, thetap
orfinalize
will be called?Yes. Presenting the loading has nothing to do with the pipe, so use
tap
(which is like an external execution that does not modify the result of the pipe). If the filter matches, the flow continues; if not, it does not emit a value (sofinalize
and tap are not called)@RRGT19 I've updated my answer with that scenario