Angular 15: Performing Parallel HTTP Calls to REST APIs using RxJs 7.8

In this article, we will see the mechanism for accessing external REST APIs by making parallel calls using RxJs.  Angular, being the great framework for building complex front-end apps, various times we need to make HTTP calls to REST APIs to fetch data to show on UI. Of course, Angular provides HttpClientModule and HttpClient classes to manage asynchronous HTTP calls. The HttpClient provides get(), post(), put(), and delete() methods to perform HTTP operations and all these methods return an Observable object.  Objeservable is the most basic building block provided in RxJs. An Observable represents a set of values received over any time, this means that when the HTTP call returns data from REST API it is immediately delivered to UI so that the UI can be updated. 

There is one more use case that must be considered which makes parallel HTTP calls to REST APIs to fetch data. Here, the challenge is how to collect these multiple observables at a time and pass them to UI? Figure 1 shows the situation of making parallel HTTP calls



Figure 1: Parallel HTTP Calls to REST API          

Figure 1 explains the use case for parallel HTTP calls. We must use the RxJs object model to make sure that handle data received from these parallel HTTP calls made by the Angular application. We can use RxJx operators as follows

  • pipe()
    • We use this function to return a resolved value from the observable.
  • map()
    • It transforms the provided source object to the new output object based on the condition.
  • forkJoin()  
    • We use forkJoin() when we have received multiple completed Observables and we want to collect the final emitted observable value. In the current use case, when we perform parallel HTTP calls we will be receiving multiple observables, using forkJoin() we will be able to make sure that all observables are collected and delivered to UI.
We will create an Angular application using Angular CLI and REST APIs using Node.js and Express.

Step 1: Open the Command Prompt (or Terminal window if using Linux or macOS) and run the following command to install Angular CLI
    
    npm install --global @angular/cli

Step 2: Run the following command to create a new Angular application using Angular CLI

    ng new parallel-calls

This command will create an Angular application in a parallel-calls folder. Navigate to the parallel-calls folder and run the following command to install express, cors, and bootstrap packages

    npm install --save express cors bootstrap

Step 3: Open the application in Visual Studio Code by opening the parallel-calls folder in Visual Studio Code. In this application folder, add a new folder named server. In this folder, add new JavaScript files named customersservice.js and ordersservice.js. We will add the code for creating Express REST APIs.   Listing 1 shows the code for Customer Service

import express from 'express';
import cors from 'cors';


const Customers = [
  {CustomerId:101,CustomerName:'MSIT', City:'Pune-East'},
  {CustomerId:102,CustomerName:'LSIT', City:'Pune-West'},
  {CustomerId:103,CustomerName:'TSIT', City:'Pune-South'},
  {CustomerId:104,CustomerName:'DNCD', City:'Pune-North'},
  {CustomerId:105,CustomerName:'DEVC', City:'Pune-East'},
  {CustomerId:106,CustomerName:'DNTV', City:'Pune-West'},
  {CustomerId:107,CustomerName:'MSTV', City:'Pune-South'},
  {CustomerId:107,CustomerName:'TSTV', City:'Pune-East'},
  {CustomerId:108,CustomerName:'LSTV', City:'Pune-North'},
  {CustomerId:109,CustomerName:'VPIT', City:'Pune-East'},
  {CustomerId:110,CustomerName:'SAIT', City:'Pune-West'}
];

const PORT =  process.env.PORT || 7011;
// create an instance
const instance = express();
// Add JSON Middleware in HTTP Pipeline
instance.use(express.json());
// do not parse incoming data other than HTTP Request Message Body
instance.use(express.urlencoded({extended:false}));
// configure CORS
instance.use(cors({
    origin: "*",
    methods: "*",
    allowedHeaders: "*"
}));

instance.get('/api/customers', (req,resp)=>{
   resp.status(200).send(Customers);
});

instance.listen(PORT, ()=>{
  console.log('Customer Service Started on Port');
});

Listing 1: CustomersService

Listing 2 shows the code for the Orders Service

import express from 'express';
import cors from 'cors';


const Orders = [
  {OrderId:'A-Ord-101', OrderedItem:'Laptop', Quantity:10},
  {OrderId:'A-Ord-102', OrderedItem:'Desktop', Quantity:30},
  {OrderId:'A-Ord-103', OrderedItem:'Printers', Quantity:50},
  {OrderId:'A-Ord-104', OrderedItem:'HDD', Quantity:500},
  {OrderId:'A-Ord-105', OrderedItem:'SDD', Quantity:3000},
  {OrderId:'A-Ord-106', OrderedItem:'USB', Quantity:7000},
  {OrderId:'A-Ord-107', OrderedItem:'Mouse', Quantity:10000},
  {OrderId:'A-Ord-108', OrderedItem:'Keyboard', Quantity:8000},
  {OrderId:'A-Ord-109', OrderedItem:'Adapter', Quantity:2000},
  {OrderId:'A-Ord-110', OrderedItem:'Display', Quantity:5000},
];

const PORT =  process.env.PORT || 7012;
// create an instance
const instance = express();
// Add JSON Middleware in HTTP Pipeline
instance.use(express.json());
// do not parse incoming data other than HTTP Request Message Body
instance.use(express.urlencoded({extended:false}));
// configure CORS
instance.use(cors({
    origin: "*",
    methods: "*",
    allowedHeaders: "*"
}));

instance.get('/api/orders', (req,resp)=>{
   resp.status(200).send(Orders);
});

instance.listen(PORT, ()=>{
  console.log('Customer Service Started on Port');
});

Listing 2: OrdersService

Here, we created two REST APIs using Node and Express. Now it's time for us to write code to access them using the Angular application. 

Step 4: In the src folder of the Angular application add a folder and name it as models. In this folder, we will add model classes by adding customers.js and orders.js files. The code in these files is provided in Listing 3

customers.js

export class Customers{
 [x:string]:any;
  constructor(
    public CustomerId:number,
    public CustomerName:string,
    public City:string
  ){}
}


orders.js

export class Orders{
  [x:string]:any;
  constructor(
    public OrderId:string,
    public OrderedItem:string,
    public Quantity:number
  ){}
}

Listing 3: The customers.js and orders.js
 
Step 5: In the src folder, add a new folder and name it as services. In this folder, we will add JavaScript files containing Angular Services to make HTTP calls to REST APIs. In these services, we will be using HttpClient class to make HTTP calls. To receive and manage the response we will be using RxJs Observable class. To resolve and process Observable we will be using the pipe() and map() methods. The code for CustomerClientService class is shown in Listing 4

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable,pipe,map } from 'rxjs';
import { Customers } from 'src/models/customers';

@Injectable({providedIn: 'root'})
export class CustomerClientService {
  private url = 'http://localhost:7011/api/customers';
  constructor(private httpClient: HttpClient) { }

  get():Observable<Customers[]>{
    const resp = this.httpClient.get<Customers[]>(this.url);
    return resp.pipe(map(result=>result));
  }

}

Listing 4: CustomerClientService

The code OrderClientService is shown in Listing 5


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable,pipe,map } from 'rxjs';
import { Orders } from 'src/models/orders';

@Injectable({providedIn: 'root'})
export class OrderClientService {
  private url:string ='http://localhost:7012/api/orders';
  constructor(private httpClient: HttpClient) { }

  get():Observable<Orders[]> {
    const resp = this.httpClient.get<Orders[]>(this.url);
    return resp.pipe(map(result=>result));
  }
}

Listing 5: The OrderClientService

Step 6: In the app sub-folder of src folder, add a new Angular component in the app.table.component.ts file as shown in Listing 6. We will create a reusable TableCompoennt to show data received from the REST APIs


import { Component, Input, OnInit } from '@angular/core';

@Component({
  selector: 'app-table-component',
  templateUrl: './app.table.view.html'
})

export class TableComponent implements OnInit {

  private _DataSource:Array<any>;
  tableColumns: Array<string>;
  constructor() {
    this._DataSource = new Array<any>();
    this.tableColumns = new Array<string>();
  }

  ngOnInit() {

  }

  @Input()
  set DataSource(val:Array<any>){
    this._DataSource = val;
    this.tableColumns = Object.keys(this._DataSource[0]);
      }

  get DataSource():Array<any>{
    return this._DataSource;
  }

}

Listing 6: TableComponent

The DataSource property will be used to bind the data received from the services into the table. Let's add a new HTML file in the app folder containing HTML UI for the TableComponent. The name of this file will be app.table.view.html.  The HTML code for this file is shown in Listing 7

<table class="table table-striped table-dark table-bordered table-sm"
cellspacing="0"
width="100%">
  <thead>
    <tr>
      <th *ngFor="let h of tableColumns">{{h}}</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let r of DataSource">
        <td *ngFor="let h of tableColumns">
          {{r[h]}}
        </td>
    </tr>
  </tbody>
</table>

Listing 7: HTML UI for the TableComponent.

Step 7: Modify the AppComponent from the app.component.ts to make a call to Angular services created in Step 5. In this component, we will receive Observables by invoking methods from Angular services. These received Observables will be resolved using forkJoin() method and emitted data from them will be used by the component to update on UI.  Listing 8 shows code for the AppComponent


import { Component, OnInit } from '@angular/core';
import { forkJoin } from 'rxjs';
import { Customers } from 'src/models/customers';
import { Orders } from 'src/models/orders';
import { CustomerClientService } from 'src/services/customerclientservice';
import { OrderClientService } from 'src/services/orderclientservice';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  customersData: Array<Customers>;
  ordersData:Array<Orders>;
  // Inject Angular Services
  constructor(
    private custServ:CustomerClientService, private ordServ:OrderClientService
  ){
    this.customersData = new Array<Customers>();
    this.ordersData = new Array<Orders>();
  }

  ngOnInit(): void {
    // Receive Observables
    const customers$ = this.custServ.get();
    const orders$ = this.ordServ.get();
    // Resolve these observables to read emited data
    // from Bbservables
    forkJoin([customers$,orders$]).subscribe(result=>{
      // read the emitted data from the received
      // Observables
       this.customersData = result[0];
       this.ordersData = result[1];
    });
  }
}

Listing 8: The AppComponent            

As shown in Listing 8, we have the Angular services injected into the component. The component access methods form these services in ngOnInit() method. The customers$ and orders$ observable objects are used to subscribe to the observable returned from  Angular Services. Using the forkJoin() method these observables are resolved and the Customers and Orders data is read from them. Thus the forkJoin() is used to handle these parallel calls and resolve the Observables.  To show data on UI, modify the app.component.html to show the TableCompoent with the data for Customers and Orders as shown in Listing 9

<h1>Calling Services Parallely using RxJs</h1>

<h2>Customers Data</h2>

<app-table-component [DataSource]="customersData"></app-table-component>

<hr/>

<h2>Orders Data</h2>

<app-table-component [DataSource]="ordersData"></app-table-component>


Listing 9: The app.component.html

The UI for AppComponent uses the TableComponent that we have created in Step 6. 

Step 8: Modify the angular.json to use the bootstrap class shown in Listing 10


 "styles": [
              "src/styles.css",
              "./node_modules/bootstrap/dist/css/bootstrap.min.css"
            ]
Listing 10: angular.json for using Bootstrap

Step 9: Install the following package to run an Angular application and Node Express services
    
    npm install --global nodemon
 
    npm install --global npm-run-all

Step 10: Modify the package.json to run the Angular application and Node Express APIs as shown in Listing 11 (red marked)


"scripts": {
    "ng": "ng",
    "custservice":"nodemon ./server/customersservice.js",
    "ordservice":"nodemon ./server/ordersservice.js",
    "ngclient":"ng serve",
    "start": "npm-run-all -p custservice ordservice ngclient",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
Listing 11: The package.json modification to run the Angular app and REST APIs

Run the Application from the command prompt by navigating to the parallel-calls folder by running the following command

    npm run start

This will start the Angular application and the Express REST APIs. Open the browser  and browse the http://localhost:4200 address the page will be loaded with Customers and Orders data displayed in it as shown in Figure 2



Figure 2: The Result
  
The code for this article is available on this link

Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach