Angular RxJS: two nested https and update component when first one is reach

吃可爱长大的小学妹 提交于 2019-12-11 15:40:05

问题


I need to perform two http requests. One after first one, since values provided by first one are going to be used to make the other http calls.

I also need to update my component when first http is resolved. And also, updated it when second http is also resolved.

interface Vehicle {
    readonly id: string;
    readonly name: string;
    readonly brand: string;
}

First http call is getting name, and the other one the brand.

Guess this html component:

<div *ngFor='let vehicle of vehicles$ | async'>
    {{vehicle.name}} - {{vehicle.brand}}
</div>

In controller:

vehicles$: Observable<Array<Vehicle>>
ngOnInit() {
    this.vehicles$ = this._vehicleService.getVehicles();
    // how to go on???
}

where getVehicles:

getVehicles(): Observable<Array<Vehicle>> {
    return http.get("url")
        .map(vehicle => <Vehicle>{id: vehicle.id, name: vehicle.name});
}

getVehicleBrand(ids: Array<String>): Observable<Array<VehicleBrand>> {
   // build url according ids parameter
   return http.get("url")
       .map(brand => <VehicleBrand>{id: brand.vehicle_id, brand.name});
}

How could I merge Vehicle with VehicleBrand?


回答1:


Merging data between requests shouldn't be the responsibility of the component. That's what services are for.

Example of what such a service might look (feel free to customize it according to your own needs of course):

const vehiclesIdNameMock: VehicleIdName[] = [
  { id: 'vehicle-1', name: 'Vehicle 1' },
  { id: 'vehicle-2', name: 'Vehicle 2' },
  { id: 'vehicle-3', name: 'Vehicle 3' },
];

const vehiclesIdToBrandMap: { [key: string]: VehicleIdBrand } = {
  'vehicle-1': { id: 'vehicle-1', brand: 'Brand A' },
  'vehicle-2': { id: 'vehicle-2', brand: 'Brand A' },
  'vehicle-3': { id: 'vehicle-3', brand: 'Brand B' },
}

@Injectable()
export class VehiclesService {
  private getVehiclesIdName(): Observable<VehicleIdName[]> {
    return of(vehiclesIdNameMock).pipe(delay(500));
  }

  private getVehicleIdBrand(id: string): Observable<VehicleIdBrand> {
    return of(vehiclesIdToBrandMap[id]).pipe(delay(500));
  }

  public getVehicles(): Observable<Vehicle[]>{
    return this.getVehiclesIdName().pipe(
      mergeMap(vehiclesIdName => {
        const requests: Observable<VehicleIdBrand>[] = vehiclesIdName.map(vehicleIdName => this.getVehicleIdBrand(vehicleIdName.id))

        return forkJoin(requests).pipe(
          map(vehiclesIdBrand => vehiclesIdBrand.map(
            (vehiclesIdBrand, index) => ({ ...vehiclesIdBrand, ...vehiclesIdName[index] })
          ))
        );
      })
    )
  }
}

The 2 private methods here are mocked because I obviously don't have access to your endpoint but as long as your return the same data type that'll be fine.

Then your VehicleComponent would be as clean as:

@Component({
  selector: 'app-vehicles',
  templateUrl: './vehicles.component.html',
  styleUrls: ['./vehicles.component.css']
})
export class VehiclesComponent {
  public vehicles$: Observable<Vehicle[]> = this.vehiclesService.getVehicles();

  constructor(private vehiclesService: VehiclesService) {}
}

Here's a live demo on Stackblitz https://stackblitz.com/edit/angular-et3oug




回答2:


You could do something like this:

http.get("url").pipe(
    map(vehicle => <Vehicle>{id: vehicle.id, name: vehicle.name})
    tap(vehicle => { this.vehicles$.next(vehicle); })
    switchMap(vehicle => this.getVehicleBrand([vehicle.id]));
)



回答3:


You can use async await here is the example.

Controller

vehicles$: Observable<Array<Vehicle>>
ngOnInit() {
      this.getVehicle(); 
}

async getVehicle() {
    this.vehicles$ = this._vehicleService.getVehicles(["1", "2"]);
    await getVehicleBrand([vehicalIds]);
    // other code
}

getVehicleBrand(ids: Array<String>): Observable<Array<VehicleBrand>> {
return new Promise(resolve => { 
   // build url according ids parameter
   http.get("url")
       .map(brand => <VehicleBrand>{id: brand.vehicle_id, brand.name});
    resolve();
  })
}



回答4:


If you want to return observables in a sequential manner, you can make use of RxJS's mergeMap to map over the observable values from the service's getVehicles() into an inner observable, to get the list of ids. Then, we call the getVehicleBrand() method from the service, and the observables are returned in subscribe() on the subsequent line.

From there, you can match the brand name with the respective vehicles.

On your component.ts, this is how it will look like

ngOnInit() {
  this._vehicleService.getVehicles(["1", "2"]).pipe(
    mergeMap(vehicles => {
      const vehicleIdList: string[] = vehicles.map(vehicle => vehicle.id);
      return this._vehicleService.getVehicleBrand(vehicleIdList);
    })
  ).subscribe(res => {
    // continue the rest
  })
}


来源:https://stackoverflow.com/questions/56270672/angular-rxjs-two-nested-https-and-update-component-when-first-one-is-reach

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!