Using forkJoin with an array of objects in TypeScript

Imagine a scenario where there is a web service that returns basic data in the form of CustomerGroups:

{
    0: {
        Name: 'Group 1',
        Customers: {
            ...
        }
    },
    1: {
        Name: 'Group 2',
        Customers: {
            ...
        }
    }
}
...and another web-service that provides detailed customer data:
{
    CustomerNo: 1,
    Street: 'Test Street 123',
    PostCode: '99999',
    City: 'Blabla',
    ...
}

The goal is to combine the results of both services using ForkJoin within an Angular4 injectable service. However, encountering challenges when trying to retrieve specific details for each customer:

ReadAll(useCache?: boolean): Observable<ICustomerGroup[]> {
    if (!this.customerGroupCache || !useCache) {
        return this.http.get(this.urlGetAll)
            .map((res: Response) => res.json())
            .flatMap((customerGroups: any[]) => {
                ...
            })
            .catch((error: any) => Observable.throw(error.message || 'Server error'));
    }

    return Observable.of(this.customerGroupCache);
}

How can ForkJoin be utilized to iterate over each "CustomerGroup" and fetch detailed information for each "Customer"? Is it feasible to use "forEach" inside a "ForkJoin"?

The expected outcome after applying "ForkJoin" should resemble:

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

Solution

As suggested by taras-d, incorporating mergeMap was missed initially to combine results from multiple Observables. The revised code snippet is as follows:

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

    return Observable.of(this.customerGroupCache);
}

Merging all components together:

private buildCustomerGroupArray(allGroups: any, allCustomers: any): Array<ICustomerGroup> {
    let result: Array<ICustomerGroup> = Array<ICustomerGroup>();
    allGroups.forEach((group, index) => {
        ...
    });

    return result;
}

Answer №1

The simplest approach is to gather all groups and customers first, and then merge them together.

export class AppComponent {

  // Dummy 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> {

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

      const customersObservables = [];

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

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

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

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

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

      // Receive all groups and customers here
      const [ allGroups, allCustomers ] = res;

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

      // TODO: Merge the results

    });
  }

}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Obtaining data from JSON using JSON path

Attempting to retrieve the id value tied to an email value from the given JSON data has been a challenge for me: [ {"id":11,"username":"John","address":"London","email":"<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="84eeebece ...

Accessing information from req.files in a TypeScript environment

I am currently using multer for uploading images. How can I retrieve a specific file from req.files? Trying to access it by index or fieldname has been unsuccessful. Even when I log it, I see that it's a normal array, so I suspect the issue may be rel ...

How to extract the chosen option from a bound list within an Angular application

Our goal is to avoid using 2-way binding in our component setup: <select type="text" formControlName="region" (change)="regionChanged($event)"> <option *ngFor="let region of regionsDDL" [ngValue]="region">{{region.name}}</option> ...

Plot data points from geojson onto a leaflet map using markers

How can I efficiently import geoJson data (containing over 2000 coordinates) into a leaflet map? Below is a brief snippet of geo json: { "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { ...

Is it recommended to employ cluster connection within my Redis client when utilizing Azure Redis Cluster?

It seems that the Azure documentation on clustering can be a bit confusing. According to the docs: Will my client application need any modifications to support clustering? Once clustering is activated, only database 0 will be accessible. If your client ...

Angular2: Issue with service injection within service

Below is the code snippet I have written. Assuming you can grasp the purpose without further elaboration. @Injectable() export class Dispatcher { } @Injectable() export class TodoStore { constructor(@Inject(Dispatcher) private dispatcher:Dispatcher ...

Individual static item within Material Table's DataSource

Can a single static object be passed instead of a list of User information in Material Table DataSource? User object with data - {idUser:1, lastName: "xyz", firstName: "abc" } Where idUser is obtained from a URL parameter. And displayed using the Materi ...

Is it possible to open a PDF in Acrobat directly from a single button click on a user interface created with Angular/JS?

Currently, I am in the process of developing an Angular/java webpage that will allow users to interact with various forms. At the moment, if a user wants to edit a PDF, they must download it and then go to their downloads folder to open it in Adobe Acrobat ...

Using ajax to submit a form in CodeIgniter and displaying the returned data in a table view

When I submit a form on the view page, I want the form data to be saved in a database table and displayed without refreshing the page. Below is my view code: <div class="col-md-12"> <div class="row"> <div> <table class="table table-s ...

Struggling to streamline if conditions within a for loop?

I am struggling with optimizing code that involves multiple if conditions within a for loop. I know there must be a more efficient way to do this, but I can't seem to figure it out. Thank you so much for your assistance! list1 = list() list2 = list() ...

AngularJS Constants in TypeScript using CommonJS modules

Let's talk about a scenario where I need to select a filter object to build. The filters are stored in an array: app.constant("filters", () => <IFilterList>[ (value, label) => <IFilterObject>{ value: value, label: label } ]); i ...

The window:beforeunload event in IE 11 always triggers the unsaved changes dialogue box

When it comes to adding a listener for the beforeunload event on the global window object, IE 11 behaves differently compared to Chrome and Firefox. This is particularly noticeable when using Angular's "ngForm" module. If a form is dirty and has not ...

The specified file is not located within the 'rootDir' directory in the Cypress tsconfig.json file

I've encountered an issue while configuring Cypress in my Project, specifically with the typescript setup for Cypress. Here is the structure of my project: fronend/ - cypress/ -tsconfig.json - src/ - tsconfig.json - package.jso ...

Building Custom Request Types for a Personalized Express Router in TypeScript (TS2769)

I've been facing challenges integrating custom Request types with TypeScript. Within my application, I have both public and private routes. The public routes utilize the Request type from Express. On the other hand, the private routes make use of a ...

Issues with JSON parsing functionality in Swift

Struggling with developing an IOS app in Xcode 6 using swift, I am finding myself frustrated and confused due to my lack of experience in the language. Attempting to view JSON results from an API call to LastFM using NSLog("%@", allResults), I encountered ...

Importing JSON Array Information into a MySQL Database

I'm feeling lost when it comes to accessing and importing this data into MySQL. The JSON Data I have is: { "serial_number": "70-b3-d5-1a-00-be", "dateTime": "2020-08-14 20:58", "passReport" ...

Unexpected JSON end causes issue with DELETE request in Next.js version 13

I am currently working on a web app using Next 13 and I have a route.ts file located in the api folder. This file contains two different methods, POST and DELETE. While both methods successfully receive the request, I am facing an issue with JSON parsing ...

Angular 4: Modifying the URL without the Component being Displayed

I'm currently facing an issue where I am attempting to link a component from one component using routerLink = "selected" const routes: Routes = [ { path: '', children: [ { path: 'account&apo ...

Angular2 is throwing a Typescript Reference error because 'is not defined' in the context

I've been grappling with this specific error for the last couple of hours, and it's definitely not a run-of-the-mill undefined error. The issue arises when I define a value in the webpack plugins section (for a global variable) to serve as an API ...

How to utilize methods from different pages in Ionic 2

Looking to display the total number of items in an array on a card located on the home screen, but facing issues referencing methods outside of the typescript file they're written in. Trying to extract the array size method and utilize it in a differe ...