Search code examples
angularrxjsangular2-observablesrxjs-observables

Join multiple http calls that can happen conditionally in one observable


For the sake of the example lets say I have a entity called client:

interface Client {
   id: number
   name: string
   address: Address[]
}

interface Address {
   id: number
   street: string
}

In this case I cannot update the name of client and his addresses in the same call.

Instead I need to:

  • Check the address to be created then create one by one (and store the value to be used later)
  • Check the address to be updated then updated one by one (and store the value to be used later)
  • And finally do another call to chenge the name of the client.

So what I want to do is join this multiple http calls that can happen conditionally in one observable.

This is what I have so far:

updateClientAndClientAddresses(): Observable<Client> {       

   return of({}).pipe(
     ******* Conditionally make all the create addresses calls *******
     ******* Conditionally make all the update addresses calls *******
     *******   Then make the update name call *******

     switchMap(([updateNameResponse: Client, responseOfAllAddressesToBeCreated: Address, responseOfAllAddressesToUpdated: Address]) => {
    // Ideally here I will have the results of all the calls made so far.

    // Merge everything manually here
    return {
      ...updateClientCallResponse,
      addresses: [
        ...responseOfAllAddressesToBeCreated,
        ...responseOfAllAddressesToUpdated
      ]};
  }),
)
}

Just to reinforce: Before I do the call to update the name I need to check if:

  • addressesToBeCreated.length if yes I need to do one particular call for every lenght of this array, wait for the response and store the result to be used latter on.

then I need to check if:

  • addressesToUpdated.length if yes I need to do another particular call vor every lenght of this array and wait for the response and store the result to be used latter on.

  • Finally I will do the call to update the name od the client, get the result and then merge with all the responses that I conditionally made before.


Solution

    1. Organize addresses into two groups: those to be created, and those to be updated.

      const addressesToBeCreated: Address[] = [ /* list of addresses to be created */ ];
      const addressesToBeUpdated: Address[] = [ /* list of addresses to be updated */ ];
      
    2. Collect the HTTP requests needed to create and update the addresses, respectively. (Assumptions were made with what the server response content will be.)

      const createRequests: Observable<Address>[] = addressesToBeCreated.map(
        (address) => this.client.post(...) // 👈 request server to save the address, and respond with the created address resource
      );
      
      const updateRequests: Observable<Address>[] = addressesToBeUpdated.map(
        (address) => this.client.put(...) // 👈 request server to update existing address, and respond with the updated address resource
      );
      
    3. Construct a pipeline using operators forkJoin and concatMap, in a way that tells RxJS to actually trigger sending the create and update requests, and then return said addresses that were created and updated.

      const savedAddresses$ = forkJoin(createRequests) // 👈 trigger the bunch of HTTP requests that creates addresses
        .pipe(
          concatMap((createdAddresses) => {  // 👈 now we have a list of created addresses
            return forkJoin(updateRequests)  // 👈 next, trigger HTTP requests that updates addresses
              .pipe(
                map((updatedAddresses) => {  // 👈 now we have the list of updated addresses
                  return [                   // 👈 emit a tuple of created and updated addresses down the pipeline
                    createdAddresses,
                    updatedAddresses
                  ];
                })
              );
          })
        );
      

      savedAddresses$ will be an observable that emits an array of two elements: a list of created addresses, and list of updated addresses.

    4. Finally do another call to change the name of the client.

      const savedModels$ = savedAddresses$.pipe(
        concatMap((savedAddresses) => {
          return this.client.put(...)   // 👈 request server to update client name, and return updated client model
            .pipe(
              map((updatedClient) => {  // 👈 now we have the client object with updated name
                return [updatedClient, ...savedAddresses]; // 👈 emit a tuple down the pipeline with three elements: client, list of created addresses, and list of updated addresses
              })
            );
        })
      );
      

      savedModels$ will be an observable that emits an array of three elements: client model with its name updated, list of addresses created, and list of addresses updated.

    5. At this point, you can finally "merge everything manually here." You can join data about the client, and addresses that have been created and updated however you like.

      updateClientAndClientAddresses(): Observable<Client> {
        ... // 👈 code from previous steps
      
        return savedModels$.pipe(
          map(([updatedClient, createdAddresses, updatedAddresses]) => {
            return {
              ...updatedClient,
              address: [
                ...createdAddresses,
                ...updatedAddresses
              ]
            };
          })
        );
      }