Why are my 4 API calls in Angular 2 not waiting for the completion of the previous call before running?

My form or component requires 4 separate API calls to complete a "save" operation. Although only one call needs to wait for the other 3, I decided to chain all of them together for added security.

This is how I have set it up:

save() {
    this.save1(saveObject1)
        .subscribe(response => {
            if (response === 0) {
                this.processErrorResult('save1');
            }

            this.save2(saveObject2)
                .subscribe(response2 => {
                    if (response2 === 0) {
                        this.processErrorResult('save1');
                    }

                    this.save3(saveObject3)
                        .subscribe(response3 => {
                            if (response3 === 0) {
                                this.processErrorResult('save1');
                            }

                            if (this.noErrorsInFirst3Saves()) {
                                this.save4(saveObject4)
                                    .subscribe(response4 => {
                                        if (response4 === 0) {
                                            this.processErrorResult('save1');
                                        }

                                        if (!this.hasErrors()) {
                                            this.router.navigate(['confirmation']);
                                        }
                                    });
                            }
                        });
                });
        });
}

private save1(saveObject: any): Observable<int> {
    this.api.save1(saveObject)
        .subscribe(successful => {
            return Observable.of(1);
        }, failed => {
            return Observable.of(0);
        });
}

private save2(saveObject: any): Observable<int> {
    this.api.save2(saveObject)
        .subscribe(successful => {
            return Observable.of(1);
        }, failed => {
            return Observable.of(0);
        });
}

private save3(saveObject: any): Observable<int> {
    this.api.save3(saveObject)
        .subscribe(successful => {
            return Observable.of(1);
        }, failed => {
            return Observable.of(0);
        });
}

private save4(saveObject: any): Observable<int> {
    this.api.save4(saveObject)
        .subscribe(successful => {
            return Observable.of(1);
        }, failed => {
            return Observable.of(0);
        });
}

I've included log messages in each save{number} function to track their execution order and response retrieval.

Upon clicking save, the page almost immediately redirects to the confirmation page. However, I notice in Chrome Dev Tools that API responses start coming in after the redirection.

Why does this setup not properly chain the transactions? In Angular 1, using promises like .then(response => {}); made chaining easier. Is there something different about API calls causing the duplicated network requests?

The network panel in Chrome Dev Tools always shows 2 calls for every API request: an initial Options request followed by the actual get, post, put, or delete. This behavior seems unfamiliar compared to my previous projects, possibly indicating normal API practices.

Any insights on this issue?

Edit: It's been suggested to make changes as per Harry's answer below.

I'm attempting to implement the advice but facing some confusion with the implementation. Here's a detailed view of my setup:

private save1(saveObject: any): Observable<number> {
    if (saveObject.hasChanges()) {
        return this.api.save1(saveObject)
            .map(successful => {
                return this.parseSaveResponse(successful, saveObject, true); // true indicates a successful API response
            })
            .catch(failed => {
                return this.parseSaveResponse(successful, saveObject, false); // false indicates a failed API response
            });
    } else {
        return Observable.of(-1); // no changes, return immediately
    }
}

private parseSaveResponse(response: any, saveObject: any, transactionSuccessful: boolean) {
    this.logService.logTransaction({transactionType}, saveObject, response, transactionSuccessful);

    if (!transactionSuccessful) {
        // add error to be displayed on the form
        return 0;
    }

    return 1;
}

At this stage, I encounter an error at the .catch({}); line stating:

Argument of type '(error: any) => 0 | 1' is not assignable to parameter of type '(err: any, caught: Observable<0 | 1>) => ObservableInput<{}>'. Type '0 | 1' is not assignable to type 'ObservableInput<{}>'. Type '0' is not assignable to type 'ObservableInput<{}>'.)

Answer №1

Your code has several issues that may be causing problems. Firstly, make sure your save1/save2/save3 methods only return the observable from your API so that you can subscribe to it.

Also, avoid using the int type in TypeScript and switch to using number instead.

private save1(saveObject: any): Observable<number> {
  return this.api.save1(saveObject);
}
// or
private save1(saveObject: any): Observable<number> {
  return this.api.save1(saveObject).map(response => 1).catch(response => 0)
}

If you need to modify the emitted items, use the map operator to transform them accordingly.

Lastly, refrain from subscribing to an observable within another observable. Instead, chain observables together using the flatMap operator to flatten emissions into a single observable.

save() {
  this.save1(saveObject1)
    .flatMap(response1 => this.save2(saveObject2))
    .flatMap(response2 => {
      if (response2 === 0) {
        // handle error
      }
      return this.save3(saveObject3);
    })
    .flatMap(() => this.save4(saveObject4))
    .subscribe(response4 => {
      // do redirect
    });
  }

Visit this link for more information on flatMap operator

Answer №2

It's important to remember to "return" your observables for proper functionality. For example:

Instead of:

this.api.save1(saveObject)

Use:

return this.api.save1(saveObject)

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

What methods work most effectively for gathering user behavior data in a web application?

While I may not be a seasoned professional web developer, I have gained some experience with front-end frameworks like Angular 4 and Vue 2.0, as well as other tools that have allowed me to create web applications (SPA) and utilize AWS Lambda for the backen ...

What is the process of accessing the changelog.md file within a component in Angular?

My challenge is to showcase the content from my changelog.md file, which is situated at the same level as the package.json file. I created a service for this purpose using the following code, function getData() { return http.get('../c ...

Interpret information in Angular 2 using Typescript

Just starting with Angular (IONIC) and need help. How can I extract the userId or id from this code? his.response = data. //Looking for guidance on accessing Json keys Response : { "userId": 1, "id": 1, "title": "sunt aut facere repellat providen ...

Using matTooltip to display 2 different values

I am working with a list that contains sublists of operations, and I needed to add tooltips to specific items in the list. Here is how I accomplished it: <li *ngFor="let op of opList" style="margin-right: 5px;margin-left: 5px;" class="elementLi ...

Storing data in Angular 2 services for safekeeping

I have multiple sub-components that each receive specific data from a main component. These sub-components only receive the data necessary for display purposes. My goal now is to create a service that can make a request to a server. The problem is, this re ...

Assign a value to a FormControl in Angular 6

I have 60 properties linked to 60 controls through the mat-tab in a form. When it comes to editing mode, I need to assign values to all the controls. One approach is as follows: this.form.controls['dept'].setValue(selected.id); This involves l ...

Header remains fixed when scrolling

Is there a way to make the header of a Bootstrap table fixed when scrolling, while also adjusting the width of each column based on the content within it? I want the column headers to match the width of the row with the most text. Setting the position of t ...

Is it possible to utilize a function to manipulate the [checked] attribute of a checkbox and toggle it between true and false?

I'm trying to update the checkbox's [checked] property by calling a method, but even though the method is being called, the checkbox status doesn't change. HTML <div*ngFor="let vest_style of VEST_STYLE"> <input type="checkbox" ...

The header of the data table does not adapt well to different screen

I am currently experiencing an issue with my Angular data table. Everything works fine until I add the option parameter "scrollY: '200px'" or "scrollY: '50vh'", which causes a bug in my table header. It becomes unresponsive, and the siz ...

RXJS: utilizing observables within observables

I am currently struggling with the task of returning a UserDetail object that comprises a User and Results. The User is retrieved using the accessToken of an Account, all acquired through individual asynchronous calls. My dilemma lies in finding a way to e ...

Customizing the Position of Material UI Select in a Theme Override

I'm trying to customize the position of the dropdown menu for select fields in my theme without having to implement it individually on each select element. Here's what I've attempted: createMuiTheme({ overrides: { MuiSelect: { ...

Component in Next.js fetching data from an external API

I am attempting to generate cards dynamically with content fetched from an API. Unfortunately, I have been unsuccessful in finding a method that works during website rendering. My goal is to pass the "packages" as properties to the component within the div ...

Using Reactive Forms group in router-outlet

I'm encountering a problem while trying to share my Reactive Forms among multiple components, specifically in the context of routing. The error message I see is: 'Can't bind to 'group' since it isn't a known property of &apos ...

Set the mat-option as active by marking it with a check symbol

Currently, I am utilizing mat-autocomplete. Whenever a selection is made manually from the dropdown options, the chosen item is displayed with a distinct background color and has a checkmark on the right side. However, when an option in the dropdown is se ...

Tips on clearing and updating the Edit Modal dialog popup form with fresh data

This code snippet represents my Edit button functionality. The issue I am facing is that I cannot populate my Form with the correct data from another component. Even when I click the (Edit) button, it retrieves different data but fails to update my form, ...

Error: The file type you are trying to upload is not supported

my document-upload.service.ts private uploadFile(file: File) { let formData: FormData = new FormData(); formData.append('uploadFile', file, file.name); let headers = new HttpHeaders({'Content-Type': 'multip ...

When executing tests in jest, imports from node_modules may become undefined

My jest configuration seems to be encountering an issue with resolving node_modules during execution. They are coming back as undefined... Here is a snippet from my test file: import lodash from 'lodash' it('test', () => { expect ...

Angular HTTP client implementation with retry logic using alternative access token

Dealing with access tokens and refresh tokens for multiple APIs can be tricky. The challenge arises when an access token expires and needs to be updated without disrupting the functionality of the application. The current solution involves manually updati ...

Delete row from dx-pivot-grid

In my current project, I am utilizing Angular and Typescript along with the DevExtreme library. I have encountered a challenge while trying to remove specific rows from the PivotGrid in DevExtreme. According to the documentation and forum discussions I fo ...

Encountering an error while compiling the Angular 8 app: "expected ':' but got error TS1005"

As I work on developing an Angular 8 application through a tutorial, I find myself facing a challenge in my header component. Specifically, I am looking to display the email address of the currently logged-in user within this component. The code snippet fr ...