If the condition is met, go ahead and run the switchMap function; if not, make sure to cancel everything

Working on a web application using Angular 10 has led me to prefer Observables over Promises. However, creating a seamless flow of executions can be challenging at times.

My objective is to follow this flow:

  1. Show a confirmation modal
  2. If the confirmation returns a value ({isConfirmed: true}), display a loading screen (based on the condition).
  3. Utilize switchMap to initiate HTTP calls.
  4. If any HTTP call fails, hide the loading screen and display a warning message.

My Approach:

 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 canceling the confirmation, the error event is triggered without displaying the loading screen.

The first switchMap contains two HTTP calls, one returning a Promise and the other an Observable. This is why I used from() for the first call.

My Objectives are:

  1. To conditionally enter the switchMap if the confirmation result is isConfirmed: true. If false, abort the process without triggering the error event.
  2. To determine the optimal placement for tap or finalize to handle showing/hiding the loading screen and displaying success/error messages to the user.

Answer №1

1

Execute the switchMap only if the confirmation result is isConfirmed: true.

Consider using filter. For example:

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

This will emit values that meet the filter condition.

2

When is the ideal place to include tap or finalize...?

  • finalize: Should be placed at the end to ensure it runs even if a prior subscription fails.

  • tap: Can be used wherever an action needs to be executed. For instance, for printing users' emails, the tap instruction should be inserted in the pipe where that data exists.


In summary:

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 final tap acts as the subscriber of the subscription outcome, hence its position at the end. However, this code snippet should belong inside the subscribe function.

UPDATE (from comments)

To complete the observable when the condition is false, consider implementing the following logic:

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 accordingly. If it's false, the flow will bypass subsequent operations and transition to finalize.

Answer №2

In my opinion, tackling this problem could be approached in the following manner:

return from(isConfirmed)
    .pipe(
        switchMap(res => res.isConfirmed ? of(res) : EMPTY),
        tap(() => this.loadingService.present()),
        switchMap(res => from(this.service.httpCallOneGetUsers()).pipe(...)
        ...

The tweak I've made is incorporating switchMap and tap() right at the start. By checking if res.isConfirmed === false, you can return EMPTY which will not trigger any subsequent operators (it's essentially a complete notification). In cases where it's true, the original res object is returned using of(res).

For concealing the loading process, the optimal operator to use would most likely be finalize() since it executes when the chain is disposed, after completion, encountering errors, or being unsubscribed.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The value of req.headers('Authorization') has not been defined

I'm experiencing difficulty with my code as the token is coming back as undefined. Here is the frontend section: export const fetchUser = async (token: any) => { const res = await axios.post('/user/getuser', { headers ...

constructor parameters not being flagged as unused by no-unused-vars plugin

I have a variable in the constructor that is not being used, and only tsc seems to pick up on it. Even though I have tried various plugins and recommendations to get eslint to recognize the unused variable, it still doesn't work. I have experimented ...

How can I add a new key value pair to an existing object in Angular?

I'm looking to add a new key value pair to an existing object without declaring it inside the initial object declaration. I attempted the code below, but encountered the following error: Property 'specialty' does not exist on type saveFor ...

What is the correct way to define the onClick event in a React component?

I have been encountering an issue while trying to implement an onClick event in React using tsx. The flexbox and button are being correctly displayed, but I am facing a problem with the onClick event in vscode. I have tried several ideas from the stack com ...

After extraction from local storage, the type assertion is failing to work properly

I have a unique situation in my Angular project where I have stored an array of data points in the local storage. To handle this data, I have created a custom class as follows: export class Datapoint { id: number; name: string; // ... additional pr ...

Tips for concealing boostrap spinners

Within the main component, I have an @Input() alkatreszList$!: Observable<any[]>; that gets passed into the child component input as @Input() item: any; using the item object: <div class="container-fluid"> <div class="row ...

What methods can TypeScript use to accommodate this kind of Generic Type?

It appears that there is already an existing GitHub issue related to this topic. You can find it here: ts#1213. This type of usage resembles a high-order function, and I am unsure if TypeScript supports it. Although the interface remains the same, there ...

How to Add External Applications Using Angular-CLI

I am a newcomer to using angular-cli and I have a question regarding the installation of the npm library mdbootstrap. Following the guidelines provided here in the Angular CLI Instructions, I attempted to install mdbootstrap by executing the following step ...

Using LINQ with ngFor in Angular 6

Within the component.ts, I extract 15 different lookup list values and assign each one to a list, which is then bound to the corresponding <select> element in the HTML. This process functions correctly. Is there a method to streamline this code for ...

Encountering an issue while trying to import the validator module in NextJS 13

I encountered a peculiar issue while trying to import a module. Nextjs presented the following error message: ./application/sign_in/sign_in_store.ts:2:0 Module not found: Can't resolve 'validator' 1 | import { createEvent, createStore } fr ...

Tips for correctly decorating constructors in TypeScript

When a class is wrapped with a decorator, the superclasses lose access to that classes' properties. But why does this happen? I've got some code that demonstrates the issue: First, a decorator is created which replaces the constructor of a cla ...

What is the process of submitting form data in Angular?

I am in the process of submitting form data to a server that has CORS disabled, meaning it only accepts POST requests and does not allow OPTIONS preflight requests. I have read that if the request is a Simple Request (POST with content type =application/x- ...

Effortless loading with tab navigation in routers

Encountering an issue with router navigation and lazy loaded modules. When lazy loading is removed, everything works fine. Here is my setup specifically for lazy loaded modules: In the app.template file, I have the main router-outlet defined. The root ap ...

Multiple conditions in TypeScript resulting in a function output

I am working on developing a function that can return different types based on its parameters. Similar to the function below, but I want it to be more streamlined and efficient (using generics without union types as results) type ResultType = { get: Get ...

Exploring the depths of Rx.ReplaySubject: Techniques for delaying the `next()` event

Confused Mind: Either I'm mistaken, or the whiskey is starting to take effect. (I can't rule out the possibility that I'm just going crazy. Apologies for that.) Assumption: My assumption was that ReplaySubject would emit a single value eve ...

Using a for loop in conjunction with Observable.forkJoin

In my component, I am striving to populate an array known as processes, containing individual process objects that each have a list of tasks. Currently, I am dealing with two API calls: /processes and /process/{processId}/tasks I utilize the /processes ...

Monitoring mouse events on a child element with an attached directive in Angular 7

Is it possible to capture mouse events that occur only within the child element of the directive's host element using @HostListener()? <div listnerDirective class="parent"> <div class="child> <------ Listen for mouse events here ...

What is the best way to effectively handle the proxying of objects across multiple levels?

As illustrated in a Stack Overflow thread, utilizing Proxy objects is an effective method for monitoring changes in an object. But what if you need to monitor changes in subobjects? In such cases, you will also have to proxy those subobjects. I am curren ...

Angular2's service executing http method is limited to just once

I have a service that is responsible for retrieving information from the server. The goal is to execute the request only once and then share the data with every component in the application. Currently, my code looks like this: @Injectable() export class P ...

What is the easiest way to compile a single .ts file in my src folder? I can achieve this by configuring the tsconfig.js file and running the yarn

{ "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, ...