Parallel HTTP calls from Angular

Angular being an extensible framework is widely used for developing high responsive front end modern web applications.

These modern web applications consist of several complex workflows on the server side. To provide access of these workflows to client applications, we need REST APIs so that client applications can access these APIs over HTTP.

Client applications developed using  Front-End frameworks like Angular can make use of HttpClient object from @angular/common/http to access these REST APIs and then using Observable object from RxJs Angular, can store and manage data received from the REST APIs in the front-end application.

But what if the Angular application wants to makes call to REST APIs in Parallel? 





This could be tricky but an important requirement from the customer.  In the following figure I have explained the scenario



Figure 1: Scenario of accessing multiple REST APIs from Angular Client Application.

If we need to find a solution on this then it is important for us to to understand the UX requirements for the Angular application to define a strategy for making parallel calls.

Advantages of making external Parallel calls are that, the UI blocking time can be reduced. As shown in the above figure, an end-user wants to show all states, cities and employees  on the page when the page is loaded by making three different calls to REST APIs. In this case, if calls are made sequentially then an asynchronous HTTP operations may delay the overall loading time of the page. The parallel HTTP calls from Angular applications can be beneficial. The question here is how to make parallel HTTP calls and receive data?

As mentioned earlier, Angular makes use of RxJs Library and its Observable class to store response received from the HTTP calls.

Observable class

The Observable is the most basic building class of RxJs library. This call represents set of values of any types. The Observable class needs the subscriber so that values stored in the Observable can be provided to the subscriber. The following figure shows the behavior of the Observable class


Figure 2: The Observable class

forkJoin() Function  

The forkJoin() function accepts an array of Observable and returns Observable. This returned Observable emits an array of values that is returned from the REST APIs. The forkJoin() functions waits for observable to complete and emits data when all observables are complete. In the following figure forkJoin() is explained.



Figure 3: The forkJoin() function       

Creating REST APIs using ASP.NET Core


We will be implementing an application using REST APIs created using ASP.NET core. The ASP.NET Core application is created using Visual Studio 2019 and .NET Core 2.2.
(Note: For simplicity, I have not used the database for fetching data instead, I have created collection classes for State, Cities and Employees).

Step 1: Open Visual Studio 2019 and create an ASP.NET Core WEB API Application.  Name the application as DataServices. In the project, add a Models folder and in this folder, add three class files named as State.cs, City.cs and Employee.cs. Add the following code in these files:

namespace DataServices.Models
{
    public class State
    {
        public int StateId { get; set; }
        public string StateName { get; set; }
    }
}


Listing 1: State.cs

 public class City
    {
        public int CityId { get; set; }
        public string CityName { get; set; }
        public int StateId { get; set; }
    }

Listing 2: City.cs

public class Employee
    {
        public int EmpId { get; set; }
        public string EmpName { get; set; }
        public int CityId { get; set; }
    }

Listing 3: Employee.cs

In the project, add a new folder and name it as DataStore. In this folder, add a new class file and name it as Databases.cs and add the following code in it

  public class StateList : List
    {
        public StateList()
        {
            Add(new State() { StateId = 1, StateName = "Maharashtra" });
            Add(new State() { StateId = 2, StateName = "Gujarat" });
            Add(new State() { StateId = 3, StateName = "Rajasthan" });
            Add(new State() { StateId = 4, StateName = "Karnataka" });
        }
    }

    public class CityList : List
    {
        public CityList()
        {
            Add(new City() { CityId = 101, CityName = "Pune", StateId = 1 });
            Add(new City() { CityId = 102, CityName = "Ahmedabad", StateId = 1 });
            Add(new City() { CityId = 103, CityName = "Jaypur", StateId = 1 });
            Add(new City() { CityId = 104, CityName = "Bengalure", StateId = 1 });
            Add(new City() { CityId = 105, CityName = "Mulbai", StateId = 1 });
            Add(new City() { CityId = 106, CityName = "Surat", StateId = 2 });
            Add(new City() { CityId = 107, CityName = "Udaipur", StateId = 3 });
            Add(new City() { CityId = 108, CityName = "Hubli", StateId = 4 });
            Add(new City() { CityId = 109, CityName = "Nashik", StateId = 1 });
            Add(new City() { CityId = 110, CityName = "Baroda", StateId = 2 });
            Add(new City() { CityId = 111, CityName = "Jodhpur", StateId = 3 });
            Add(new City() { CityId = 112, CityName = "Belgaum", StateId = 4 });
            Add(new City() { CityId = 113, CityName = "Nagpur", StateId = 1 });
            Add(new City() { CityId = 114, CityName = "Rajkot", StateId = 2 });
            Add(new City() { CityId = 115, CityName = "Bikaner", StateId = 3 });
            Add(new City() { CityId = 116, CityName = "Mysuru", StateId = 4 });
        }
    }

    public class EmployeeList : List
    {
        public EmployeeList()
        {
            Add(new Employee() { EmpId = 10001, EmpName = "Akash", CityId = 101 });
            Add(new Employee() { EmpId = 10002, EmpName = "Kumar", CityId = 102 });
            Add(new Employee() { EmpId = 10003, EmpName = "Sachin", CityId = 103 });
            Add(new Employee() { EmpId = 10004, EmpName = "Rahul", CityId = 104 });
            Add(new Employee() { EmpId = 10005, EmpName = "Mandar", CityId = 101 });
            Add(new Employee() { EmpId = 10006, EmpName = "Kiran", CityId = 102 });
            Add(new Employee() { EmpId = 10007, EmpName = "Avinash", CityId = 103 });
            Add(new Employee() { EmpId = 10008, EmpName = "Nishant", CityId = 104 });
            Add(new Employee() { EmpId = 10009, EmpName = "Vaibhav", CityId = 101 });
            Add(new Employee() { EmpId = 10010, EmpName = "Mukesk", CityId = 102 });
            Add(new Employee() { EmpId = 10011, EmpName = "Vivek", CityId = 103 });
            Add(new Employee() { EmpId = 10012, EmpName = "Sandeep", CityId = 104 });
            Add(new Employee() { EmpId = 10013, EmpName = "Satish", CityId = 101 });
            Add(new Employee() { EmpId = 10014, EmpName = "Vinay", CityId = 102 });
            Add(new Employee() { EmpId = 10015, EmpName = "Sanjay", CityId = 103 });
            Add(new Employee() { EmpId = 10016, EmpName = "Sharad", CityId = 104 });
            Add(new Employee() { EmpId = 10017, EmpName = "Vijay", CityId = 101 });
            Add(new Employee() { EmpId = 10018, EmpName = "Vilas", CityId = 102 });
            Add(new Employee() { EmpId = 10019, EmpName = "Abhay", CityId = 103 });
            Add(new Employee() { EmpId = 10020, EmpName = "Keshav", CityId = 104 });
            Add(new Employee() { EmpId = 10021, EmpName = "Anil", CityId = 101 });
            Add(new Employee() { EmpId = 10022, EmpName = "Abhay", CityId = 102 });
            Add(new Employee() { EmpId = 10023, EmpName = "Jaywant", CityId = 103 });
            Add(new Employee() { EmpId = 10024, EmpName = "Shyam", CityId = 104 });
            Add(new Employee() { EmpId = 10025, EmpName = "Anil", CityId = 101 });
            Add(new Employee() { EmpId = 10026, EmpName = "Suhas", CityId = 102 });
            Add(new Employee() { EmpId = 10027, EmpName = "Kaustubh", CityId = 103 });
            Add(new Employee() { EmpId = 10028, EmpName = "Naineesh", CityId = 104 });
            Add(new Employee() { EmpId = 10029, EmpName = "Arav", CityId = 101 });
            Add(new Employee() { EmpId = 10030, EmpName = "Sujay", CityId = 102 });
            Add(new Employee() { EmpId = 10031, EmpName = "Krushna", CityId = 103 });
            Add(new Employee() { EmpId = 10032, EmpName = "Tejas", CityId = 104 });
            Add(new Employee() { EmpId = 10033, EmpName = "Rahul", CityId = 101 });
            Add(new Employee() { EmpId = 10034, EmpName = "Sameer", CityId = 102 });
            Add(new Employee() { EmpId = 10035, EmpName = "Piyush", CityId = 103 });
            Add(new Employee() { EmpId = 10036, EmpName = "Abhishek", CityId = 104 });
            Add(new Employee() { EmpId = 10037, EmpName = "Prashant", CityId = 101 });
            Add(new Employee() { EmpId = 10038, EmpName = "Ashish", CityId = 102 });
            Add(new Employee() { EmpId = 10039, EmpName = "Harshad", CityId = 103 });
            Add(new Employee() { EmpId = 10040, EmpName = "Shreeniwas", CityId = 104 });
            Add(new Employee() { EmpId = 10041, EmpName = "Pankaj", CityId = 101 });
            Add(new Employee() { EmpId = 10042, EmpName = "Abhijit", CityId = 102 });
            Add(new Employee() { EmpId = 10043, EmpName = "Ashwin", CityId = 103 });
            Add(new Employee() { EmpId = 10044, EmpName = "Vaibhav", CityId = 104 });
            Add(new Employee() { EmpId = 10045, EmpName = "Prashant", CityId = 101 });
            Add(new Employee() { EmpId = 10046, EmpName = "Vinit", CityId = 102 });
            Add(new Employee() { EmpId = 10047, EmpName = "Mahesh", CityId = 103 });
            Add(new Employee() { EmpId = 10048, EmpName = "Rahul", CityId = 104 });
        }
    }

Listing 4: Database classes

The above code contains hard-coded data in it. (Note: You can use database access code here using EF Core)

Step 2: In the Controllers folder add three Empty API Controllers. These controllers will access Database classes to returns States, Cities and Employees. The code of the controller class is provided as in the following code

[Route("api/[controller]")]
    [ApiController]
    public class StateController : ControllerBase
    {
        public IActionResult Get()
        {
            var states = new StateList();
            return Ok(states) ;
        }
    }

Listing 5: StateController.cs


[Route("api/[controller]")]
    [ApiController]
    public class CityController : ControllerBase
    {
        public IActionResult Get()
        {
            var cities = new CityList();
            return Ok(cities) ;
        }
    }

Listing 6: CityController.cs


 [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        public IActionResult Get()
        {
            var emps = new EmployeeList();
            return Ok(emps) ;
        }
    }

Listing 7: EmployeeController.cs

Step 3: Since an Angular application from a different domain will access the REST APIs, we need to configure CORS policy for the APIs created in the Step 2. Lets modify the ConfigureServices() method and Configure() method of the Startup class as shown in the following listing

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(policy=> {
                policy.AddPolicy("corspolicy", rules=> {
                    rules.AllowAnyOrigin()
                         .AllowAnyMethod()
                         .AllowAnyHeader();
                });
            });
            services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver
              = new DefaultContractResolver()).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseCors("corspolicy");
            app.UseMvc();
        }

Listing 8: The Startup.cs class

Creating Angular Front-End Application

Lets create an Angular application. We will be creating this application using Angular CLI. Create a new project of name MultiHttpCalls using Angular CLI. This will create a folder of name MultiHttpCalls. Open this folder in Visual Studio Code.

Step 4: In the app sub-folder of the src folder, add a new file and name it as app.model.ts. Add the following three classes in this file.

export class State {
  constructor(
    public StateId: number,
    public StateName: string
  ) { }
}

export class City {
  constructor(
    public CityId: number,
    public CityName: string,
    public StateId: number
  ) { }
}

export class Employee {
  constructor(
    public EmpId: number,
    public EmpName: string,
    public CityId: string
  ) {

  }
}


Listing 9: Model classes of name State, City and Department
  
These classes will be used for Databinding on  HTML element

Step 5: Add a new file of name app.service.ts. In this file, lets add an Angular service. This service will contain methods to make Http GET calls to REST APIs created in earlier Steps.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { State, City, Employee } from './app.models';




@Injectable({
  providedIn: 'root'
})
export class MultiCallService {
  private url: string;

  constructor(private http: HttpClient) {
    this.url = 'http://localhost:10156/api';
  }

  getStates(): Observable<State[]> {
    let states: Observable = null;
    states = this.http.get(`${this.url}/State`);
    return states;
  }

  getCities(): Observable<City[]> {
    let cities: Observable = null;
    cities = this.http.get(`${this.url}/City`);
    return cities;
  }

  getEmployees(): Observable<Employee[]> {
    let employees: Observable = null;
    employees = this.http.get(`${this.url}/Employee`);
    return employees;
  }
}


Listing 10: The Angular Service 

The above code contains MultiCallService class. This class is injected with HttpClient class. There are three methods to make HTTP GET  requests for State, City and Employee REST APIs. All these methods returns Observable objects.

Step 6: Modify the app.component.html file as shown in Listing 11:


<div class="container-12">
    <table class="table table-bordered table-striped">
        <tr>
            <td>
                <select class="form-control">
          <option value="">Select State</option>
          <option *ngFor="let s of states" value="{{s.StateId}}">{{s.StateName}}</option>
        </select>
            </td>
            <td>
                <select class="form-control">
          <option value="">Select City</option>
          <option *ngFor="let c of cities" value="{{c.CityId}}">{{c.CityName}}</option>
        </select>
            </td>
        </tr>
    </table>
    <hr>
    <div class="container-12">
        <table class="table table-bordered table-striped">
            <thead>
                <tr>
                    <td>EmpId</td>
                    <td>EmpName</td>
                    <td>CityId</td>
                </tr>
            </thead>
            <tbody>
                <tr *ngFor="let e of employees">
                    <td>{{e.EmpId}}</td>
                    <td>{{e.EmpName}}</td>
                    <td>{{e.CityId}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Listing 11: The Html for the component
   
Step 7: In the AppModule, import HttpClientModule so that HttpClient injection in the Angular service can be executed.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule, HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }


Listing 12: AppModule class

Step 8: Modify the app.component.ts file. The AppComponent class is injected with the MultiCallService. This class implements the OnInit interface and its ngOnInit() method. The AppComponent class imports forkJoin() method to make parallel calls to the REST APIs. Modify the following code in the AppComponent class.

import { Component, OnInit } from '@angular/core';
import { MultiCallService } from './app.service';
import { State, Employee, City } from './app.models';
import { forkJoin } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  states: Array;
  cities: Array;
  employees: Array;

  constructor(private serv: MultiCallService) {
    this.states = new Array();
    this.cities = new Array();
    this.employees = new Array();
  }

  ngOnInit(): void {
    const statePromise = this.serv.getStates();
    const citiesPromise = this.serv.getCities();
    const employeesPromise = this.serv.getEmployees();
    // forkJoin() to make parallel calls
    forkJoin([statePromise, citiesPromise, employeesPromise]).subscribe(responses => {
        this.states = responses[0];
        this.cities = responses[1];
        this.employees = responses[2];
    });
  }
}



Listing 13: AppComponent class 

The ngOnInit() method makes calls to getStates(), getCities() and getEmployees() methods from service class. All these methods returns Observable objects. These Observable objects array is passed to forkJoin() method as input parameter. This method returns an Observable object. This method waits for all Observables to complete and emit results and stores the result in states, cities and employees array defined in AppComponent class. 

Run the REST API application from Visual Studio  and Angular application using the following command from the command prompt.

npm run start

Open browser and enter the http://localhost:4200 address in the address bar. This will show the following result


Figure 4: The Result loaded with all data

The above results shows all records fetched in Parallel calls. We can see these parallel calls from the browser developer tools as shown in the following figure:


Figure 5: Parallel calls on the browser

Conclusion: The forkJoin() method from RxJs is quite useful to make parallel HTTP Calls from Front-End applications created in frameworks like Angular.





About The Author

Mahesh Sabnis is a Microsoft MVP having over 18 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). He also blogs regularly at DotNetCurry.com. Follow him on twitter @maheshdotnet

No comments: