Angular 2 why aren't my 4 api calls waiting for the previous call to finish before executing?

370 views Asked by At

I have a form/component that has to do 4 separate API calls in order to do a "save". Really, only one of those transactions needs to wait for the other 3, but I wanted to chain all 4 of them anyway just to be safe.

Here's how I have it setup:

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);
        });
}

Inside each of those save{number} functions I have log messages so I know exactly the order that these functions get executed (and responses come back).

When I click save, it [almost] immediately redirects to my confirmation page, then I can sit there and watch my console window in chrome dev tools, and see the responses from the API calls start to come in.

Why is this setup not accepting the explicit chaining of these transactions? I know in Angular 1, it was very easy to do this with the promise, and doing a .then(response => {});.

The only thing that I can think of that is different, is for EVERY API call, whether it's a get/post/put/delete, it always shows 2 calls in the network panel of chrome dev tools. The first call has the RequestMethod set to Options, while the subsequent call is the corresponding get/post/put/delete with the RequestMethod set accordingly.

I don't remember ever seeing those (duplicated calls) in any of my recent applications I've worked on, (maybe I just never paid attention as much) so maybe that's just the standard-operating-procedure for API calls.

Any ideas here?

Edit: In response to Harry's suggested answer below

I'm trying to implement your answer, but still a little confused on how to implement, here's a closer look at 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 successful API response
            });
    } else {
        return Observable.of(-1); // no changes necessary, return immediately
    }
}

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

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

    return 1;
}

When getting it to this point, it's throwing me an error on the .catch({}); line saying:

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<{}>'.)

2

There are 2 answers

2
Harry On BEST ANSWER

Theres quite a few issues with your code which might be contributing. Firstly your save1/save2/save3 methods should just return the observable from your API which you will be able to subscribe to.

Secondly you're using the int type which doesn't exist in Typescript. Use number.

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 wanted to modify the items emitted you could add the map operator which maps the item emitted to something else.

Lastly you should avoid subscribing to an observable from within an observable. You can chain observables together using the flatMap operator. This transforms the items emitted by an Observable into Observables, then flatten the emissions from those 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
    });
  }

http://reactivex.io/documentation/operators/flatmap.html

5
vidalsasoon On

I think you need to "return" your observables. Ex:

Change this:

this.api.save1(saveObject)

To:

return this.api.save1(saveObject)