mits
mits

Reputation: 51

Angular 8 + RxJS and problem with combine flatMap and forkJoin

I'm trying to make multiple http requests depends of previous one and combine returned data to final result object.

I have two endpoints:

  1. /properties which returns me something like that:

    { 
      "data":[ 
        { 
          "id":1,
          "name":"Property 1",
          "units":[ 
            { 
              "id":1,
              "name":"Property 1 - Unit 1",
            },
            { 
              "id":2,
              "name":"Property 1 - Unit 2",
            },
          ],
        },
        { 
          "id":2,
          "name":"Property 2",
          "units":[ 
            { 
              "id":3,
              "name":"Property 2 - Unit 3",
            }
          ]
        }
      ]
    }```
    
  2. /properties/${property.id}/units/${unit.id}/bookings which returns me some object related to previous request for instance:

    {
      "data": {
        "id": 1
      },
      "meta": {
        "count": 5
      }
    }
    

What I would like to achieve is combine those two responses into one on single unit level. Like this:

{
  [
    ...

    { 
      "id":2,
      "name":"Property 2",
      "units":[ 
        { 
          "id":3,
          "name":"Property 2 - Unit 3",
          "bookings_meta": {
            "count":5
          }
        }
      ]
    }
  ]
}

I created something like this and it works fine:


    list(): Observable<any> {
      return this.http.get('/properties').pipe(
        map((results: any) => results.data),

        flatMap((properties: any[]) => {
          if (properties.length > 0) {
            return forkJoin(properties.map((property: any) => {
              return forkJoin(property.units.map((unit: any) => {
                return this.http.get(`/properties/${property.id}/units/${unit.id}/bookings`).pipe(
                  map(({ meta }) => {
                    unit.bookings_meta = meta
                    return unit;
                  })
                )
              }))
            }))
          }
          return of([]);
        })
      );
    }


    this.list().subscribe(response => {
        console.log(response)
    })

But I think it is not in 100% correct solution. I'm feeling there are too many forkJoin and maybe it shouldn't be there flatMap?

The requests for bookings between units should be independent.

Some one have an idea how to improve the code above?

Upvotes: 1

Views: 1465

Answers (2)

mits
mits

Reputation: 51

Finally I found an answer for the observable hell :)

list(): Observable<any> {
  return this.http.get('/properties').pipe(
    map((results: any) => results.data),

    mergeMap((properties: any[]) => forkJoin(
      properties.map((property: any) => forkJoin(
        property.units.map((unit: any) => this.http.get(`/properties/${property.id}/units/${unit.id}/bookings`).pipe(
          map(({ meta }) => ({ ...unit, meta }))
        ))
      )).pipe(map((units: any[]) => ({ ...property, units }))))
    ))
  );
}

Cheers!

Upvotes: 1

StepUp
StepUp

Reputation: 38209

forkJoin will return data when all calls are finished and return result

const combined = Observable.forkJoin(
  this.http.get('https:// ...properties').map((res: Response) => res.json()),
  this.http.get('https:// ..properties/${property.id}/units/${unit.id}/bookings')
      .map((res: Response) => res.json())
)

combined.subscribe(latestValues => {
    console.log( "latestValues" , latestValues );
});

UPDATE:

It is possible to transform items emitted by an Observable into another Observable by using the flatMap operator. It creates an inner Observable and flats its result to the outer stream.

list(): Observable<any> {
    return this.http.get('/properties')
        .flatMap(properties => properties.map(p => p.units.map(unit => 
        {
            this.http.get(`/properties/${property.id}/units/${unit.id}/bookings`)   
        })))
        .subscribe(res => { // some actions });
}

Upvotes: 1

Related Questions