Angular 15: Calling the REST API with RxJS CombineLatest

This article is an extension of my previous article. In the previous article, we have seen how to make parallel HTTP calls with RxJS forkJoin() method. In this article, we will use the combineLatest() operator of the RXJS. We use the combineLatest() operator when we need to call multiple APIs and combine them for some processing before returning the result. The best use of this operator is when you have multiple, long-lived observables that rely on each other for some calculation or determination. The use case is that the Angular application wants to access product information of different categories from various REST APIs but the Angular Application wants to show all products of received categories on UI by combining them. Figure 1 explains the use case.



Figure 1: Using the combineLatest

The combineLatest() operator accepts Observables by accessing REST APIs, and then further allows us to add the code to process these observables as per our requirements in the Angular client application. One important feature of the combineLatest() is that it will not emit an initial value until each observable passed to it emits at least one value. 

The Implementation

The code for this article is added in the same application, which I created for the previous article for forkJoin().  I have also provided the GitHub link for the code. 

Step 1: In the server folder, add a new JavaScript file and name it electronicsservice.js. In this file, we will add the code for creating REST API using Express.js. The code is shown in Listing 1


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


const ElectronicsProducts = [
  {ProductId:1001,ProductName:'Laptop', Price:450000},
  {ProductId:1002,ProductName:'Desktop', Price:23000},
  {ProductId:1003,ProductName:'RAM', Price:4300},
  {ProductId:1004,ProductName:'DVD Writer', Price:2300},
  {ProductId:1005,ProductName:'Tablet', Price:34000},
  {ProductId:1006,ProductName:'Monitor', Price:8000},
  {ProductId:1007,ProductName:'Mouse', Price:500},
  {ProductId:1008,ProductName:'Keyboard', Price:1300},
  {ProductId:1009,ProductName:'Adapter', Price:4600},
  {ProductId:1010,ProductName:'USB', Price:300}
];

const PORT =  process.env.PORT || 8011;
// 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/products/electronics', (req,resp)=>{
   resp.status(200).send(ElectronicsProducts);
});

instance.listen(PORT, ()=>{
  console.log(`Electronics Products Service Started on Port ${PORT}`);
});

Listing 1: The electronicsservice.js

Similarly, in the services folder, add a new JavaScript file and name it electricalservice.js to REST API. The code for this service is shown in Listing 2


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


const ElectricalProducts = [
  {ProductId:2001,ProductName:'Bulb', Price:45},
  {ProductId:2002,ProductName:'Mixer', Price:3000},
  {ProductId:2003,ProductName:'Socket Extension', Price:300},
  {ProductId:2004,ProductName:'Wire', Price:200},
  {ProductId:2005,ProductName:'Power Connector', Price:400},
  {ProductId:2006,ProductName:'Iron', Price:3000},
  {ProductId:2007,ProductName:'Roter', Price:500},
  {ProductId:2008,ProductName:'Lamp', Price:900},
  {ProductId:2009,ProductName:'LED Bulb', Price:600},
  {ProductId:2010,ProductName:'Tube', Price:300}
];

const PORT =  process.env.PORT || 8012;
// 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/products/electrical', (req,resp)=>{
   resp.status(200).send(ElectricalProducts);
});

instance.listen(PORT, ()=>{
  console.log(`Electronics Products Service Started on Port ${PORT}`);
});

Listing 2: The electricalservice.js

Both of the above services are returning the product information for Electronics and Electrical products.    

Step 2: In the models folder, add a new JavaScript file and name it product.js. In this file, we will add code for the Product class. This class will be used by the Angular application to store data received from API calls. Listing 3 shows the cod of the Product class


export class Product {
  constructor(
    public ProductId:number,
    public ProductName: string,
    public Price:string
  ){}
}

Listing 3: Product class

Step 3: In the service folder, we will create Angular services. These services will call REST APIs created in Step 1. In this folder, add a new JavaScript file and name it electronicsclientservice.js. In this file add the code for ElectronicsClientService as shown in Listing 4

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Product} from './../models/product';
import { map, Observable } from 'rxjs';

@Injectable({providedIn: 'root'})
export class ElectronicsClientService {

  private url:string ='http://localhost:8011/api/products/electronics';
  constructor(private httpClient: HttpClient) { }

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


Listing 4: The ElectronicsClientService

Lets' add a new JavaScript file named electicalclientservice.js to add the code to access the ElectricalService REST API. The code is shown in Listing 5


import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Product} from './../models/product';
import { map, Observable } from 'rxjs';

@Injectable({providedIn: 'root'})
export class ElectricalClientService {

  private url:string ='http://localhost:8012/api/products/electrical';
  constructor(private httpClient: HttpClient) { }

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

Listing 5: The ElectricalClientService

Step 4: In the app folder, add a new Angular component, and name the file app.combineproducts.component.ts. In this file, we will add the code for CombineProductsComponent. In this component, we will make a call to get the () method of ElectronicsClientService and ElectricalClientService Angular services using combineLatest() operator. This operator will receive Observables from the service calls and then process them so that data from Observables can be combined into the productsList array. The subscribe() method reads the process data and then assigns it to the products array. The code for the component is shown in Listing 6


import { Component, OnInit } from '@angular/core';
import { combineLatest,map } from 'rxjs';
import { Product } from 'src/models/product';
import { ElectricalClientService } from 'src/services/electricalclientservice';
import { ElectronicsClientService } from 'src/services/electronicsclientservice';

@Component({
  selector: 'app-combineproduct-component',
  templateUrl: './app.combineproduct.view.html'
})
export class CombineProductsComponent implements OnInit {

  products:Array<Product>;

  constructor(private electronicsServ:ElectronicsClientService,
    private electricalServ:ElectricalClientService) {
      this.products = new Array<Product>();
    }

  ngOnInit(): void {
    // Get the Observables from the API calls
    combineLatest({
      electronicsProducts:this.electronicsServ.get(),
      electricalProducts:this.electricalServ.get()
    }).pipe(map(resp=>{
      // Process these Observables
      const electronicsProductsList = resp.electronicsProducts;
      const electricalProductsList = resp.electricalProducts;

      const productsList:Array<Product>=new Array<Product>();

      electronicsProductsList.map((product:Product)=>{
        productsList.push(product);
      });
      electricalProductsList.map((product:Product)=>{
        productsList.push(product);
      });
      console.log(`Combine Products ${JSON.stringify(productsList)}`);
      // Return the processed Observables
      return productsList;
    })).subscribe((data)=>{
      this.products = data;
    });
  }
}

Listing 6: The CombineProductsComponent

For the component, the HTML UI will be added using the app.combineproduct.view.html file. This file will use the app-table-component to show the combined products data. (Note: The code for the TableComponent is already provided in the previous article code.). The markup code for the CombineProductsComponent is shown in Listing 7


<h1>Using RxJs CombineLatest</h1>

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

Listing 7: The CombineProductsComponent UI

The DataSource is the property exposed by the TableCompoenent which is bound with the products array defined in the CombineProductsComponent. This shows the combined products in the Table.

To run the application, modify the app.module.ts and make the CombineProductsComponent a bootstrap component. Modify the index.html and add the selector for the CombineProductsComponent in it so that the UI can be rendered. To run the Angular application along with REST APIs we need to modify the package.json as shown in Listing 8 (Red Marked)


 "scripts": {
    "ng": "ng",
    "custservice":"nodemon ./server/customersservice.js",
    "ordservice":"nodemon ./server/ordersservice.js",
    "electronicsservice":"nodemon ./server/electronicsservice.js",
    "electricalervice":"nodemon ./server/electricalservice.js",
    "ngclient":"ng serve",
    "start": "npm-run-all -p electronicsservice electricalervice ngclient",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  }

Listing 8: package.json with the command to run REST APIs

Open the command prompt and navigate to the parallel-calls folder (this is the application folder in my case. If you have cloned the git code then navigate to the folder created after cloning the code). Run the following command to run the application

    npm run start

This will start Angular Application on port 4200 and REST APIs on ports 8011 and 8012. Open the browser and browse http://localhost:4200, the Angular application will be loaded with combined products as shown in Figure 2



Figure 2: The Combined Products    

The code for this article can be downloaded from this link.

Conclusion: Using Rxjs operators we can effectively work with the REST APIs and manage the Observables easily. 

  


  


  





        

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