Search code examples
angularrxjsobservable

Angular and RxJS - merge two HTTP requests, but emit the outer one instantly


I am fairly new to Angular and RxJS, and I have a hard time wrapping my head around some aspects of RxJS operators. I have some code here (in a component):

this.salesService.getOrders().subscribe(orders => {
  this.orders = orders;
  this.orders.forEach((order, index) => {
    this.salesService.getOrderCustomers(order.id).subscribe(customers => {
      this.orders[index]['customerName'] = customers.map(c => {return c.name}).join(", ");
    })
  });
});

The nice thing about this is that when this.orders is used in a table in my template, it will be available immediately, and then the column containing the customer name will populate as each call to the getOrderCustomers service method completes.

My question is if the same can be expressed with pure RxJS, since the above does not seem to be the "RxJS way", and I would like to learn. Also, in order to isolate the fetching of customers in an effect in the NgRx ComponentStore, my understanding is that the above will not work because the effect method must return an observable.

I have attempted something along these lines:

this.salesService.getOrders().pipe(
    mergeMap(response =>
      from(response.orders).pipe(
        concatMap((oneOrder: object) => {
          return this.salesService.getOrderCustomers(oneOrder['id'])
        }),
        reduce((customers, customer) => [...customers, customer], []),
        map(customerList => {
             /*  ?? Here I would return 'response' with the customer name added into each order, 
                 but I have no indexing of the orders to work with */
        })
      )
    ),

I have two problems - one, I do not know how to add the retrieved customer into the right place in the response.orders variable, and secondly, the outer observable does not seem to emit until all the inner requests are done. Is there any RxJS way to progressively change the outer observable from the inner observable while initially emitting the outer observable in its original state?

I have a feeling that I am approaching this in a wrong, un-Angular and un-RxJS way. Any input would be appreciated. Thank you in advance.


Solution

  • Here's a solution that doesn't change that much about what you've already done (so it might be easier to understand):

    this.salesService.getOrders().pipe(
      mergeMap(orders => 
        merge(
          orders.map((order, index) => this.salesService.getOrderCustomers(order.id).pipe(
            map(customers => customers.map(c => c.name).join(", ")),
            map(customers => ({customers, index}))
          ))
        ).pipe(
          scan((acc, {customers, index}) => {
            const ordersSoFar = [...acc];
            ordersSoFar[index]['customerName'] = customers;
            return ordersSoFar;
          }, orders)
        )
      )
    ).subscribe(orders => {
      this.orders = orders;
    });
    

    The big difference here is that rather then updating orders in the background and letting Angular change detection figure out that orders has changed, this emits the updated orders every time there is an update.

    Should work the same to start with, but lets you use the observable directly in your template (without subscribing yourself) if you'd like.