RxJS: Combining multiple http requests into single, flat observable array

Viewed 1146

I am having a problem on how to approach combining multiple http get requests in parallel and returning it as a flat, observable array.

Currently, I have a method, returnNewCars(), that returns Observable<ICar[]> after making one http get request -- in the method returnAllCars(), I would like to make multiple http get requests and still return Observable<ICar[]>.

Right now, returnNewCars() prints:

(2) [{…}, {…}]
0: {Make: "Honda", Model: "CRV", Year: "2021", Specifications: Array(5)}
1: {Make: "Toyota", Model: "Camry", Year: "2021", Specifications: Array(5)}
length: 2

I would like returnAllCars() to print in the same format, but instead, with all 6 items.

I followed the RxJS doc regarding forkJoin and tried reflect it in my code, however, I don't know where to proceed from there.

app.component.ts

import { Component, OnInit } from '@angular/core';
import { CarsService } from './services/cars.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{

  title = 'AutoZone';

  constructor(private carsService: CarsService){
  }

  ngOnInit(): void {
  }
  
  testConsole(){
    this.carsService.returnNewCars().subscribe(newCars => console.log(newCars));
  }

}

cars.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from "@angular/core";
import { forkJoin, Observable } from 'rxjs';
import { concatMap, map, tap } from 'rxjs/operators';
import { ICar } from '../models/cars.model';

@Injectable({
    providedIn: 'root'
})

export class CarsService{
    carsURL = '/assets/cars.mock.json';
    newCarsURL = '/assets/cars.new.mock.json';
    preownedCarsURL = '/assets/cars.preowned.mock.json';
    usedCarsURL = '/assets/cars.used.mock.json';

    private newCars$: Observable<ICar[]>;

    //Store all http get request to new, preowned, used
    private allCars$: Observable<ICar[]>;

    constructor(private http : HttpClient){
    }
    
    returnNewCars(): Observable<ICar[]>{
        this.newCars$ = this.http.get<ICar[]>(this.newCarsURL);
        return this.newCars$;
    }

    returnAllCars(): Observable<ICar[]>{

        //How do I flatten to return Observable<ICar[]>?
        forkJoin(
            {
                new: this.http.get<ICar[]>(this.newCarsURL),
                preowned: this.http.get<ICar[]>(this.preownedCarsURL),
                used: this.http.get<ICar[]>(this.usedCarsURL)
            }
        )
        
        return null;
    }
}
2 Answers

You can simply add a .pipe(map(...)) to transform your 3 separate arrays into a single array.

I don't see any reason you need to pass an object to forkJoin, you can simply pass an array:

returnAllCars(): Observable<ICar[]>{
    return forkJoin([
        this.http.get<ICar[]>(this.newCarsURL),
        this.http.get<ICar[]>(this.preownedCarsURL),
        this.http.get<ICar[]>(this.usedCarsURL)
    ]).pipe(
        map(([new, preowned, used]) => [...new, ...preowned, ...used])
    );
}

Also, Observables are lazy, so there's no need to wrap them in a method like this:

    returnNewCars(): Observable<ICar[]>{
        this.newCars$ = this.http.get<ICar[]>(this.newCarsURL);
        return this.newCars$;
    }

You can simply define it like this:

private newCars$ = this.http.get<ICar[]>(this.newCarsURL);

So, in the case for allCars(), you could simply do:

private allCars$ = forkJoin([this.newCars$, this.preOwnedCars$, this.usedCars$])
    .pipe(
        map(([new, preowned, used]) => [...new, ...preowned, ...used])
    );

I'm not sure why do you need this logic to get all cars with one method since you should make a api call to get 3 kinds cars list separately. Anyway, I tried to make one method to get All as you expected and used forkJoin.

Here is the stackblitz link

Related