Angular2 - Multiple dependent sequential http api calls

12,900

Not sure if I totally understand your question, but here is what I do:

I make the first http call, then when the subscribe fires, it calls completeLogin. I could then fire another http call with its own complete function and repeat the chain.

Here is the component code. The user has filled in the login information and pressed login:

onSubmit() {
   console.log(' in on submit');
   this.localUser.email = this.loginForm.controls["email"].value;
   this.localUser.password = this.loginForm.controls["password"].value;
   this.loginMessage = "";
   this.checkUserValidation();
}

checkUserValidation() { 
   this.loginService.getLoggedIn()
      .subscribe(loggedIn => {
         console.log("in logged in user validation")
         if(loggedIn.error != null || loggedIn.error != undefined || loggedIn.error != "") {
            this.loginMessage = loggedIn.error;
         }
      });

      this.loginService.validateUser(this.localUser);
}

This calls the loginservice ValidateUser method

validateUser(localUser: LocalUser) {
   this.errorMessage = "";
   this.email.email = localUser.email;
   var parm = "validate~~~" + localUser.email + "/"
   var creds = JSON.stringify(this.email);
   var headers = new Headers();
   headers.append("content-type", this.constants.jsonContentType);

   console.log("making call to validate");
   this.http.post(this.constants.taskLocalUrl + parm, { headers: headers })
      .map((response: Response) => {
         console.log("json = " + response.json());
         var res = response.json();
         var result = <AdminResponseObject>response.json();
         console.log(" result: " + result);
         return result;
      })
      .subscribe(
         aro => {
            this.aro = aro
         },
         error => {
            console.log("in error");
            var errorObject = JSON.parse(error._body);
            this.errorMessage = errorObject.error_description;
            console.log(this.errorMessage);
         },
         () => this.completeValidateUser(localUser));
            console.log("done with post");
     }

completeValidateUser(localUser: LocalUser) {
   if (this.aro != undefined) {
      if (this.aro.errorMessage != null && this.aro.errorMessage != "") {
         console.log("aro err " + this.aro.errorMessage);
         this.setLoggedIn({ email: localUser.email, password: localUser.password, error: this.aro.errorMessage });
      } else {
         console.log("log in user");
         this.loginUser(localUser);
      }
   } else {
      this.router.navigate(['/verify']);
   }

}

In my login service I make a call to the authorization service which returns an observable of token.

loginUser(localUser: LocalUser) {
   this.auth.loginUser(localUser)
   .subscribe(
      token => {
         console.log('token = ' + token)
         this.token = token
      },
      error => {
         var errorObject = JSON.parse(error._body);
         this.errorMessage = errorObject.error_description;
         console.log(this.errorMessage);
         this.setLoggedIn({ email: "", password: "", error: this.errorMessage });
      },
      () => this.completeLogin(localUser));
}

In the authorization service:

loginUser(localUser: LocalUser): Observable<Token> {
   var email = localUser.email;
   var password = localUser.password;

    var headers = new Headers();
    headers.append("content-type", this.constants.formEncodedContentType);

    var creds:string = this.constants.grantString + email + this.constants.passwordString + password;
    return this.http.post(this.constants.tokenLocalUrl, creds, { headers: headers })
         .map(res => res.json())
}

The point here in this code, is to first call the validateUser method of the login service, upon response, based on the return information, if its valid, I call the loginUser method on the login service. This chain could continue as long as you need it to. You can set class level variables to hold the information that you need in each method of the chain to make decisions on what to do next.

Notice also that you can subscribe to the return in the service and process it there, it doesn't have to return to the component.

Okay, Here goes:

public getShows():any {
   this._ShowsHttpService
      .getShows()
      .subscribe(
         w => this.shows = w,
         error => this.errorMessage = error,
         () => this.completeGetShows());
}

completeGetShow() {

   //any logic here to deal with previous get;

   this.http.get#2()
      .subscribe(
         w => this.??? = w),
         error => this.error = error,
         () => this.completeGet#2);
}

completeGet#2() {

   //any logic here to deal with previous get;

   this.http.get#3()
      .subscribe(
         w => this.??? = w),
         error => this.error = error,
         () => this.completeGet#3);
}

completeGet#3() {

   //any logic here to deal with previous get;

   //another http: call like above to infinity....
}
Share:
12,900
Anthony Day
Author by

Anthony Day

Updated on June 14, 2022

Comments

  • Anthony Day
    Anthony Day almost 2 years

    I am building an Angular2 app and one of the components needs to make multiple API calls which are dependent on the previous ones.

    I currently have a service which makes an API call to get a list of TV shows. For each show, I then need to call a different API multiple times to step through the structure to determine if the show exists on a Plex server.

    The API documentation is here

    For each show, I need to make the following calls and get the correct data to determine if it exists: (Assume we have variables <TVShow>, <Season>, <Episode>)

    http://baseURL/library/sections/?X-Plex-Token=xyz will tell me: title="TV Shows" key="2"

    http://baseURL/library/sections/2/all?X-Plex-Token=xyz&title=<TVShow> will tell me: key="/library/metadata/2622/children"

    http://baseURL/library/metadata/2622/children?X-Plex-Token=xyz will tell me: index="<Season>" key="/library/metadata/14365/children"

    http://baseURL/library/metadata/14365/children?X-Plex-Token=xyz will tell me: index="<Episode>" which implies that the episode I have exists.

    The responses are in json, I have removed a lot of the excess text. At each stage I need to check that the right fields exist (<TVShow>, <Season>, <Episode>) so that they can be used for the next call. If not, I need to return that the show does not exist. If it does, I will probably want to return an id for the show.


    I have looked at lots of examples including promise, async & flatmap, but am not sure how to solve this based on the other examples I have seen.


    Here is what I have for getting the list of shows. (shows.service.ts)

    export class ShowsHttpService {
        getShows(): Observable<Show[]> {
            let shows$ = this._http
                .get(this._showHistoryUrl)
                .map(mapShows)
                .catch(this.handleError);
            return shows$;
        }
    }
    
    function mapShows(response:Response): Show[] {
        return response.json().data.map(toShow);
    }
    
    function toShow(r:any): Show {
        let show = <Show>({
            episode: r.episode,
            show_name: r.show_name,
            season: r.season,
            available : false,    // I need to fill in this variable if the show is available when querying the Plex API mentioned above.
        });
        // My best guess is here would be the right spot to call the Plex API as we are dealing with a single show at a time at this point, but I cannot see how.
        return show;
    }
    

    Here is the relevant code from the component (shows.component.ts)

    public getShows():any {
        this._ShowsHttpService
            .getShows()
            .subscribe(w => this.shows = w);
        console.log(this.shows);
    }
    

    Bonus points

    Here are the obvious next questions that are interesting, but not necessary:

    1. The first API query will be much faster than waiting for all of the other queries to take place (4 queries * ~10 shows). Can the initial list be returned and then updated with the available status when it is ready.
    2. The first Plex call to get the key="2" only needs to be performed once. It could be hard coded, but instead, can it be performmed once and remembered?
    3. Is there a way to reduce the number of API calls? I can see that I could remove the show filter, and search through the results on the client, but this doesn't seam ideal either.
    4. The 4 calls for each show must be done sequentially, but each show can be queried in parallel for speed. Is this achievable?

    Any thoughts would be much appreciated!

  • Anthony Day
    Anthony Day over 7 years
    Thanks John. The concept is spot on, but I cannot see how to integrate this with my existing code. I currently have a .get() then .map(), no .subscribe(), so not sure where to add this. Also, I need to run my sequence for each of the shows returned by the first API call. I am happy to change my existing code if observable is the wrong approach. Cheers
  • Anthony Day
    Anthony Day over 7 years
    John, I've added my component code. They now look similar. I can only see one http call in your code. I agree that when the first call is complete, the next should be fired, but cannot see how to do this and pass the right data along. I think the next call should be fired from within the service, not component as there is more processing that must be done. Thanks
  • John Baird
    John Baird over 7 years
    Okay... I've added more code to illustrate the point I'm trying to make.
  • Anthony Day
    Anthony Day over 7 years
    John, thanks so much for providing more of your code. Unfortunately, I still cannot manage to integrate your code with mine. I think I understand what you are doing, but cannot get mine to match this. Could you please show my in my code where and how to start the next HTTP call? At the moment, I am still having no luck. Thanks
  • Anthony Day
    Anthony Day over 7 years
    Thanks for all of your efforts John. It all eventually made sense! I took a slightly different approach and strung the calls together using map/flatmap rather than () =>. I'll post it later in case anyone else ever wants it, but I would have never made any progress without your help!
  • John Baird
    John Baird over 7 years
    Glad to help. Would love to see how you did it with the flatmap.