4

Let's say I've got a web service that will return basic data as follows (result is named CustomerGroups):

{
    0: {
        Name: 'Group 1',
        Customers: {
            0: {
                CustomerNo: 1,
                Name: 'Customer 1'
            },
            1: {
                CustomerNo: 2,
                Name: 'Customer 2'
            }
        }
    },
    1: {
        Name: 'Group 2',
        Customers: {
            0: {
                CustomerNo: 3,
                Name: 'Customer 3'
            },
            1: {
                CustomerNo: 4,
                Name: 'Customer 4'
            }
        }
    }
}

Furthermore i've got a web-service that will return detailed customer data:

{
    CustomerNo: 1,
    Street: 'Test Street 123',
    PostCode: '99999',
    City: 'Blabla',
    ...
}

What I want to do is to combine the result of both services using a forkJoin in an Angular4 injectable service. But I stuck when I try to request each detailed information on a given customer:

ReadAll(useCache?: boolean): Observable<ICustomerGroup[]> {
    if (!this.customerGroupCache || !useCache) {
        return this.http.get(this.urlGetAll)
            .map((res: Response) => res.json())
            .flatMap((customerGroups: any[]) => {
                if (customerGroups.length > 0) {
                    return Observable.forkJoin(
                        customerGroups.map((customerGroup: any) => {
                            return this.customerService.get(/* any CustomerNo of customerGroup.Customers */)
                            // ^---- This is my problem!!!
                        })
                    );
                }
                // No data! Return empty array
                return Observable.of([]);
            })
            .catch((error: any) => Observable.throw(error.message || 'Server error'));
    }

    return Observable.of(this.customerGroupCache);
}

How can I use the forkJoin to loop over each CustomerGroup (flatMap used to do that) and get the detailed information for each Customer? Is it possible to use forEach inside a forkJoin?

The result of forkJoin should look like:

{
    0: {
        Name: 'Group 1',
        Customers: {
            0: {
                CustomerNo: 1,
                Name: 'Customer 1',
                Street: 'Test Street 123',
                PostCode: '99999',
                City: 'Blabla'
            },
            1: {
                CustomerNo: 2,
                Name: 'Customer 2',
                Street: 'Test Street 456',
                PostCode: '888',
                City: 'Blabla'
            }
        }
    }
    ...
}

Solution

As per description by taras-d, I missed mergeMap to combine the results of multiple Observables. My final source looks like:

ReadAll(useCache?: boolean): Observable<ICustomerGroup[]> {
    if (!this.customerGroupCache || !useCache) {
        return this.http.get(this.urlGetAll)
            .mergeMap((res: Response) => {
                const customerObservables = [];
                let groups = res.json();

                groups.forEach((group, i) => {
                    group.Customers.forEach((cust, j) => {
                        customerObservables.push(this.customerService.get(cust.CustomerNo));
                    });
                });

                return Observable.forkJoin(customerObservables)
                    .map(customers => {
                        this.customerGroupCache = this.buildCustomerGroupArray(groups, customers);
                        return this.customerGroupCache;
                    });
            });
    }

    return Observable.of(this.customerGroupCache);
}

Put it all together:

private buildCustomerGroupArray(allGroups: any, allCustomers: any): Array<ICustomerGroup> {
    let result: Array<ICustomerGroup> = Array<ICustomerGroup>();
    allGroups.forEach((group, index) => {
        let newGroup = new CustomerGroup();
        newGroup.ActionFlag = ActionType.Undefined;
        newGroup.Name = group.Name;
        newGroup.OldName = group.OldName;
        newGroup.CustomerList = Array<ICustomerGroupItem>();

        group.Customers.forEach((cust, index2) => {
            if (allCustomers.find(p => p.CustomerNo === cust.CustomerNo)) {
                let currCust = allCustomers.find(p => p.CustomerNo === cust.CustomerNo);
                let newGroupItem: ICustomerGroupItem = new CustomerGroupItem({
                    ActionFlag: ActionType.Undefined,
                    CustomerName: currCust.Name,
                    CustomerNo: currCust.CustomerNo
                });

                newGroup.CustomerList.push(newGroupItem);
            }
        });

        result.push(newGroup);
    });

    return result;
}

1 Answer 1

3

Easiest way is to get all groups and all customers and then combine them.

export class AppComponent {

  // Fake customer service
  customerService = {
    getGroups() {
      return Observable.of({
          0: {
              Name: 'Group 1',
              Customers: {
                  0: {
                      CustomerNo: 1,
                      Name: 'Customer 1'
                  },
                  1: {
                      CustomerNo: 2,
                      Name: 'Customer 2'
                  }
              }
          },
          1: {
              Name: 'Group 2',
              Customers: {
                  0: {
                      CustomerNo: 3,
                      Name: 'Customer 3'
                  },
                  1: {
                      CustomerNo: 4,
                      Name: 'Customer 4'
                  }
              }
          }
      });
    },
    getCustomer(num) {
      return Observable.of({
          CustomerNo: num,
          Street: `Street ${num}`,
          PostCode: `PostCode ${num}`,
          City: `City ${num}`
      });
    }
  };

  readAll(): Observable<any> {

    // Get all groups
    return this.customerService.getGroups().mergeMap(allGroups => {

      const customersObservables = [];

      // Loop over groups
      for (let groupNum in allGroups) {
        const group = allGroups[groupNum];

        // Loop over customers in group
        for (let customerNum in group.Customers) {
          const customer = group.Customers[customerNum];

          // Create observable for every group customer
          customersObservables.push(
            this.customerService.getCustomer(customer.CustomerNo)
          );
        }
      }

      // Join all customers observable and map (return) all groups and all customers
      return Observable.forkJoin(customersObservables)
        .map(allCustomers => [allGroups, allCustomers]);
    });
  }

  ngOnInit(): void {
    this.readAll().subscribe(res => {

      // Here you will receive all groups and all customers
      const [ allGroups, allCustomers ] = res;

      console.log( JSON.stringify(allGroups, null, 2) );
      console.log( JSON.stringify(allCustomers, null, 2) )

      // TODO: Combine result

    });
  }

}
Sign up to request clarification or add additional context in comments.

3 Comments

BTW in RxJS 5 flatMap is alias for mergeMap
For cleaning up all subsriptions of the forkjoin do I need to call takeUntil at forkJoin(customersObservables).pipe(takeUntil(this.unsubscribe$)) or at this.customerService.getCustomer(customer.CustomerNo).pipe(takeUntil(this.unsubscribe$))
@dewey I think both variants are correct

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.