Angular2 - Multiple http requests that depend on e

2019-09-10 23:00发布

问题:

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.

回答1:

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...



回答2:

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


标签: angular