1

I am currently learning angular2 by recreating an app that we developed at work. Part of the app needs to make multiple http calls. Each call is dependent on the results from the previous call. I want the second, third call etc to use the id from the result of the previous call.

My component code and service code is as follows

COMPONENT:

import {Component, OnInit} from '@angular/core';
import {VillageService} from './village.service';

@Component({
    selector: 'village',
    templateUrl: 'app/village/village.component.html',
    styleUrls: ['assets/stylesheets/styles.css'],
    providers: [VillageService]
})

export class VillageComponent implements OnInit {
    villages = [];
    workAreas = [];
    shifts = [];
    roles = [];

    constructor(private _villageService: VillageService){
    }

    ngOnInit() {
        this._villageService.GetAllVillages()
        .subscribe(res => {
            this.villages = res;
        },
        null,
        () => { 
            //Get all work areas for each village
            for(let i = 0; i < this.villages.length; i++){
            this._villageService.GetAllWorkAreas(this.villages[i].VillageId)
                .subscribe(res => {
                    this.workAreas = res;
                },
                null,
                () => { 
                     //Get all shifts for each work area
                    for(let j = 0; j < this.workAreas.length; j++){
                        this._villageService.GetAllShifts(this.workAreas[j].WorkAreaId)
                        .subscribe(res => {
                            this.shifts = res;
                        },
                        null,
                        () => { 
                            //Get all roles for each shift
                            for(let k = 0; k < this.shifts.length; k++){
                               this._villageService.GetAllRoles(this.shifts[k].ShiftId)
                                .subscribe(res => {
                                    this.roles = res;
                                },
                                null,
                                () => { 
                                    this.shifts[k].roles = this.roles;
                                });
                            }  
                            this.workAreas[j].shifts = this.shifts;
                        });
                    }  
                    this.villages[i].workAreas = this.workAreas;
                    console.log(this.villages[i]); 
                });
            }
        });
    }
}

SERVICE:

import { Injectable }     from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map';

@Injectable()
export class VillageService {
    private _baseUrl = "{BASE_URL_ADDRESS_GOES_HERE}";

    constructor(private _http: Http){}

    GetAllVillages() {
        return this._http.get(this._baseUrl + "village/All").map(res =>     res.json());
    }

    GetAllWorkAreas(villageId) {
        return this._http.get(this._baseUrl + "WorkArea/All/" +   villageId).map(res => res.json());
    }

    GetAllShifts(workAreaId) {
        return this._http.get(this._baseUrl + "Shift/All/" + workAreaId).map(res => res.json());
    }

    GetAllRoles(shiftId) {
        return this._http.get(this._baseUrl + "Role/All/" + shiftId).map(res => res.json());
    }
}

The code partially works. I get all villages with their respective work areas; however, only the last village and work area has its shifts and roles. Guessing there is something wrong in my logic but can't see what it is. I also don't like doing multiple loops but couldn't think of another way to do this.

Does anyone have an idea of how to do this better? I have tried debugging to see what is happening but keep getting caught in loop hell.

Thanks in advance. Any help is appreciated.

2 Answers 2

5

Without going into the details of your code, the operator that you should be using when you such a structure (call1 => call2 => call3) where the result of one call is used for another is mergeMap (alias for flatmap in rxjs). This would look a little like this.

this._villageService.GetAllVillages()
    .flatMap(
       (response) => {
         // take from the response what you want and use it for the next call
         this._villageService.GetAllWorkAreas(//pass the thing from your previous response here)
       }
    )
    .flatMap(
      ....
    )
    .subscribe(...)

Repeat this flatMap as much as needed, for every dependent call.

What flatMap does is, it performs a function on every value, and expects that function to return an observable. Under the covers it will subscribe to that observable, and once it has a value, it will send it to the next flatmap.

So, first you get all the villages. If you have this value, it will be send to the first flatMap. The first one, will perform another request. Once that request completes, the result will be past to the next flatMap and so on...

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for your answer. I have read a little about flatMap but couldn't figure out exactly how to make it work. Will have a play around when I get a chance. Will flatMap allow each village to have a collection of work areas and each workarea have a collection of shifts etc? When I had a quick go at making it work it looked like it was adding one id of the response but not the whole collection.
if you have a list of work areas, and they each have a collection of shifts, that will not work with the example above. What I'm wondering is, why not provide a rest endpoint that provides you with all that information. Doing an x number of requests which will do another x number of request is not really performant. Better is to create a single endpoint that returns everything you need in that case.
0

it's a problem with scope of variables i and j and async js. when subscription is triggered i or j is already at the end of the loop. That's why you get only results for last village and work area. Try to wrap index variables in a immediate function eg.

for(let i = 0; i < this.villages.length; i++){
  (function(i) {
    your async code...

  })(i);  
}

1 Comment

Thank you for your response. I will have a go at getting the flatMap example above working first as most places I have read have suggested this route. If I can't get it working I will give your suggestion a shot.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.