© Venkata Keerti Kotaru 2020
V. K. KotaruAngular for Material Designhttps://doi.org/10.1007/978-1-4842-5434-9_16

16. Angular: HTTP Client

Venkata Keerti Kotaru1 
(1)
Hyderabad, India
 

An Angular application running in a browser uses remote services to retrieve and update data. The communication occurs over HTTP. Browsers widely support XMLHttpRequest (XHR) over HTTP. Out of the box, Angular provides services that ease making XHR calls in an Angular application. JSON (JavaScript Object Notation) is a good format for exchanging data between the server and a browser application. It is lightweight and efficient, considering JSON objects are simple text. Also, browsers readily interpret JSON data without needing additional libraries.

This chapter goes over getting started with an HttpClient service. We enhance the sample application to import the module and the service. Building remote services is beyond the scope of this book. To demonstrate interfacing with a typical RESTful remote service, we mock a sample service that provides and updates superhero data.

The GET, POST, PUT, and DELETE HTTP methods are largely used. The chapter explains implementing these methods for retrieve, create, update, and delete actions.

Getting Started

This chapter covers using the HttpClient Angular service that helps make XHR calls (XML HTTP Request) from an Angular application. It is part of the Angular module, HttpClientModule. We import it to make use of the HttpClient Angular service.

In the sample application, we have created three modules.
  • superheroes-material-design, which contains all the Material Design components and functionality

  • app-routing, which contains the routing logic for the single-page application

  • app, the main module for the application

Let’s create a new module encapsulating functionality for making remote HTTP calls. To create a new module using Angular CLI, run the command in Listing 16-1. We named the module app-http-calls.
ng g module app-http-calls
Listing 16-1

Generate a Module for Remote HTTP Calls

Next, create a service that makes HTTP calls. To generate a service using Angular CLI, run the command in Listing 16-2. The data service is created under the newly created app-http-calls module.
ng g service app-http-calls/superhero-data
Listing 16-2

Generate a Service for Service Calls

Note

Chapter 7 discusses building, injecting, and using Angular services. The service is a reusable TypeScript class in Angular, which can be instantiated and injected into components and other services. Unlike components and directives, services are without a user interface.

Primarily, we use a service HttpClient for making HTTP calls. HttpClientModule encapsulates the functionality for making HTTP calls. We import it into the newly created app-http-calls module. Consider Listing 16-3.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
  declarations: [],
  imports: [
    CommonModule,
    HttpClientModule
  ]
})
export class AppHttpCallsModule { }
Listing 16-3

Import HTTP Client Module

Now, a new module named AppHttpClassModule is created, and it imports HttpClientModule. To use it in the rest of the application, import it into App.Module (see Listing 16-4).
import {SuperheroesMaterialDesignModule} from './superheroes-material-design/superheroes-material-design.module';
import { AppHttpCallsModule } from './app-http-calls/app-http-calls.module';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    SuperheroesMaterialDesignModule,
    AppHttpCallsModule
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Listing 16-4

The Primary App Module Imports the New AppHttpCallsModule

We imported HttpClientModule to be able to make API calls. We created a new module, AppHttpCallsModule, and added a new service to it. This service is expected to contain the code to make HTTP calls.

AppHttpCallsModule is imported in the primary app module along with SuperheroMaterialDesignModule. The new service, Hero Data Service, can be used in all modules referenced by the app module. The service is for making HTTP calls for hero data.

A typical RESTful (REpresentational State Transfer) API allows the following HTTP methods.
  • GET: Retrieves data about one or more entities in the system. The entity could be an order, student, customer, or so forth. In the sample application, superheroes are the entities.

  • POST: Creates an entity.

  • PUT: Updates the complete entity.

  • PATCH: Updates partial data in an entity.

  • DELETE: Removes an entity from the system.

Mock Data

For the purpose of the sample application, let’s run a Node.js-based service that returns data from a mock JSON file. Consider creating a JSON file with superhero data.

JSON Server is an npm package that creates an ExpressJS-based web server from a mock JSON file. A typical service may have business logic and data access logic for saving and retrieving data from a database or a data source.

To set up JSON Server, install it using Yarn or npm. Consider the command in Listing 16-5. It is installed globally on the machine. Most dependencies install at the project level. Considering JSON Server is a developer tool used across projects, we install it at globally at the machine level.

The command might need elevated permissions on the machine. You can install it without the global flags. Remember to run the library from the installed directory.
yarn global add json-server
Or
npm install json-server –global
Listing 16-5

Install JSON Server for Mock Data

In the sample application, place a mock JSON file with superhero data in the src folder. Run the command in Listing 16-6 to start a web server that returns data from this file. Run the newly installed JSON Server.
json-server data.json
Listing 16-6

Run JSON Server

Now, access the mock response by attempting the following URL in a browser: http://localhost:3000/. In Figure 16-1, /heroes is an array object based on the structure defined in the mock JSON file.

Note

To access a mock JSON response mimicking a real server-side API, we chose JSON Server. You may use any tool for this purpose, as long as it supports RESTful services.

../images/475625_1_En_16_Chapter/475625_1_En_16_Fig1_HTML.jpg
Figure 16-1

Access mock response using JSON Server

Use HttpClient to Make HTTP Calls

The superhero data service imports HttpClient from @angular/common/http. The Angular module was imported in the preceding section.

In Listing 16-7, the service is decorated with an injectable and provided at the root. It also injects the HttpClient service. The HttpClient object is used to make an HTTP GET call (see line 11).
1. import { Injectable } from '@angular/core';
2. import { HttpClient } from '@angular/common/http';
3. import { Observable } from 'rxjs';
4. const URL_PREFIX = "http://localhost:3000";
5. @Injectable({
6.   providedIn: 'root'
7. })
8. export class SuperheroDataService {
9. constructor(private httpClient: HttpClient) { }
10.  getHeroes(){
11.    let heroes: Observable<any> = this.httpClient.get(`${URL_PREFIX}/heroes`);
12.    heroes.subscribe (
13.      (data) => console.log(data),
14.      () => ({})/** Error handling callback */,
15.      () => ({})/** Done with the observable */,
16.      );;
17.  }
18. }
Listing 16-7

HTTP Call Using HttpClient

getHeroes uses the httpClient instance and makes a GET call, which makes a remote HTTP call. It returns an observable, which is assigned to a variable named heroes (see line 11).

The subscribe function is on heroes (observable). A success handler (callback function) on the subscribe function receives and logs the JSON response from a server-side API. See line 13 in Listing 16-7 for the success callback. Figure 16-2 shows the console output and the HTTP call (in a Google Chrome Network tab). The get() function call results in the HTTP call seen in the Network tab.
../images/475625_1_En_16_Chapter/475625_1_En_16_Fig2_HTML.jpg
Figure 16-2

Console log of API response

Listing 16-8 showcases code to parse the HTTP response and create the desired object for use in the Angular application.
1  getHeroes(){
2    let heroes: Observable<any> = this.httpClient.get(`${URL_PREFIX}/heroes`);
3    heroes.subscribe (
4      (data) => {
5        console.log(data && data.forEach( (hero) => ({
6          "name":hero.name || "",
7          "email":hero.email || "",
8          "details":hero.details || "",
9         "country":hero.country || "",
10         "specialPowers":hero.specialPowers || [],
11         "cardImage":hero.cardImage || "/defaultImage.jpg",
12         "favFood":hero.favFood || []
13        })))},
14      () => ({})/** Error handling callback */,
15      () => ({})/** Done with the observable */,
16      );
17   }
Listing 16-8

Iterate to Create Hero Objects from Service Response

The subscribe function is invoked as the get function call succeeds or fails. If the GET call succeeds, the result is a list of superheroes. The success callback function parameter data is in line 4 of Listing 16-8. It is the result from the HTTP call.

We perform a null check and iterate through the list of heroes. Note lines 5 to 13. Fields from the response are assigned to a new JSON object and logged to the console. For simplicity, we are logging the data to the console. We may create a promise or an observable to provide the data to the calling function. Typically, a calling function is part of a component or another service.

Note

We cannot return the superhero object from the service function. The success callback on subscribe is invoked asynchronously. The function control returns the component class (or any other calling function) after invoking the get function on line 2. The function was already returned before executing lines 4 to 13. We create a promise or an observable to return data from this function.

Explicitly Type Response Object

An advantage of using TypeScript is that we can define a class or a type for the response structure. It helps simplify the code, and it is a good practice.

In Listing 16-9, the httpClient get function is in line 2. It uses a template of type Array of the Superhero object. The Superhero class is structured to match the API response.

Subsequently, the success handler in line 4 uses the same data type as that of the get function. Hence, we can iterate through the objects to process the data further.
1.  getHeroes(){
2.    this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`)
3.      .subscribe(
4.        (data: Array<Superhero>) => data.map( i => console.log(i.name, i.country, i.email)),
5.        () => ({/** Error handling code goes here */}),
6.        () => ({/** Observable complete */})
7.     );
8.  }
Listing 16-9

Remote Service Response Typed to a Class in Angular

Note

Unlike the prior sample, we have not created a separate object for a returned value from the get function. It returns an observable. We chained the function call with a subscribe(). It is useful, considering the heroes observable is not used in other places in the function. Creating a separate variable is redundant.

Typed objects help IDEs like Visual Studio Code provide better IntelliSense. Figure 16-3 shows a list of fields on the superhero object.
../images/475625_1_En_16_Chapter/475625_1_En_16_Fig3_HTML.jpg
Figure 16-3

Visual Studio Code IntelliSense on TypeScript object

Return Data to the Calling Function

In the preceding code samples, the Angular service logged data obtained from the remote service to the console. The sample obtains a superhero list from the remote service (mocked with JSON Server). If we used the subscribe() function in the Angular service (similar to line 3 in Listing 16-9), results from the remote service are obtained. If the results can be used as-is in the component (or any other calling function), we may avoid subscribing in the Angular service function. Listing 16-10 returns the observable returned by the get function call as-is to the calling function.
  getHeroes(): Observable<Array<Superhero>> {
      return this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`);
  }
Listing 16-10

Return Observable from HTTP Client to the Calling Function

The calling function may subscribe and use the data. In Listing 16-11, a component sets the return value on a class variable. The data is shown in the component by the template. Listing 16-12 is the HTML template for the component. Line 2 iterates through the heroes array reference.
    this.heroService
      .getHeroes()
      .subscribe(data => this.heroes = data);
Listing 16-11

Results from the Angular Service Assigned to a Class Variable

1. <mat-grid-list>
2.  <mat-grid-tile *ngFor="let hero of heroes; let i=index">
3.      <app-superhero-profile
4.      height="30%"
5.      [name]="hero.name"
6.      [lives-in]="hero.livesIn"
7.      firstAppearance="2008"
8.      [superpowers]="hero.specialPowers"
9.      [card-image]="hero.cardImage">
10.    </app-superhero-profile>
11.  </mat-grid-tile>
12.</mat-grid-list>
Listing 16-12

Results from the Angular Service Assigned to a Class Variable

Alternatively, we may return assign the observable directly to a variable used in the template. In Listing 16-13, the datatype of the variable heroes is Observable.
export class SuperheroGridListComponent implements OnInit {
  heroes: Observable<Array<Superhero>>;
  constructor(private heroService: SuperheroDataService) { }
  ngOnInit() {
    this.heroes = this.heroService
      .getHeroes();
  }
}
Listing 16-13

Assign Hero Service Result to An Observable

Use an Async pipe in the template with the observable. Note the highlighted area on line 2 in Listing 16-14.
1. <mat-grid-list>
2.  <mat-grid-tile *ngFor="let hero of heroes | async; let i=index">
3.      <app-superhero-profile
4.      height="30%"
5.      [name]="hero.name"
6.      [lives-in]="hero.livesIn"
7.      firstAppearance="2008"
8.      [superpowers]="hero.specialPowers"
9.      [card-image]="hero.cardImage">
10.    </app-superhero-profile>
11.  </mat-grid-tile>
12. </mat-grid-list>
Listing 16-14

Async Pipe on Observable

In some cases, we need to change the object structure, and process the response data with additional logic. In this scenario, the Angular service subscribes to the observable returned by the HttpClient get() function. It processes the data and returns the results to the calling function.

The function cannot directly return the results. Considering the subscribe callback function is invoked asynchronously, control from the Angular service getHeroes() function already returns to the calling function. In this scenario, we can create and return another observable or promise from the Angular service getHeroes() function.

Listing 16-15 returns an observable.
1.  getHeroes(): Observable<Array<Superhero>> {
2.    return Observable.create((observer) => {
3.      let results: Array<Superhero> = [];
4.      this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`)
5.        .subscribe(
6.          (data: Array<Superhero>) => {
7.            data.map( i => {
8.              // perform additional processing and transformation of data obtained from the service.
9.              results.push(i)
10.            });
11.            observer.next(results);
12               observer.complete();
13.          },
14.          () => ({/** Error handling code goes here */}),
15.          () => ({/** Observable complete */})
16.        );
17.    });
18.  }
Listing 16-15

Angular Service getHeroes() Function Returning Observable

Line 2 creates and returns an observable. The calling function has reference to the observable. In the calling function, the subscribe function is called on the new observable created by getHeroes().

The static create() function on the Observable class acts as a factory to create an observable. It expects a callback function with an input parameter for the observer. Results can be accessed by the subscriber (a calling function or the component), with the next() function call on the observer (see line 11).

The observable returned by the HttpClient get() function is subscribed in line 5. We iterate through the results from the remote service. Perform additional processing or transformation of the object structure. In line 8, a placeholder is used for this purpose. Afterward, we iterate through all the results, and use the next() function on the observer to send data to the subscriber in the calling function (see line 11).

Note

Follow the links in the references section at the end of chapter to learn more about RxJS and observables.

We may also return a promise (see Listing 16-16).
1.  getHeroes(): Promise<Array<Superhero>>{
2.    return new Promise<Array<Superhero>>( (resolve /*, reject */)=> {
3.      let results: Array<Superhero> = [];
4.      this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`)
5.        .subscribe(
6.          (data: Array<Superhero>) => {
7.            data.map( i => {
8.              // perform additional processing and transformation of data obtained from the service.
9.              results.push(i)
10.            });
11.            resolve(results);
12.          },
13.          () => ({/** Error handling code goes here */}),
14.          () => ({/** Observable complete */})
15.        );
16.    });
17.  }
Listing 16-16

Angular Service getHeroes() Function Returning Promise

The getHeroes() function creates and returns a promise (see line 2). The observable returned by the HttpClient get() function is subscribed in line 5. We iterate through the results from the remote service. Perform additional processing or transformation of the object structure. In line 8, a placeholder is used for this purpose.

Afterward, we iterate through all the results, and use the resolve() function, which is the first parameter in the constructor to the promise. With resolve, send data to the calling function. In this sample, the calling function is in a component. Listing 16-17 is the component making use of the data obtained from the component.
    this.heroService.getHeroes().then( data => this.heroes = data);
Listing 16-17

Angular Component Using the Promise

The results from the Angular service are set on an object in the component class; namely, heroes (this.heroes). The HTML template uses this component variable to show the results on the screen. See Listing 16-18.
1. <mat-grid-list >
2.  <mat-grid-tile *ngFor="let hero of heroes; let i=index">
3.      <app-superhero-profile
4.      height="30%"
5.      [name]="hero.name"
6.      [lives-in]="hero.livesIn"
7.      firstAppearance="2008"
8.      [superpowers]="hero.specialPowers"
9.      [card-image]="hero.cardImage">
10.    </app-superhero-profile>
11.  </mat-grid-tile>
12.</mat-grid-list>
Listing 16-18

Angular HTML Template Component Showing the Results

Note

Most HTTP services, especially the services that adhere to REST standards, return data about a single entity with the ID specified in the URL. Consider a sample URL with JSON Server, http://localhost:3000/heroes/001. It returns hero with ID 001. We can use the HttpClient instance get() method with the relevant URL to retrieve a single entity or a subset of the list.

Handling Errors with Remote Service Calls

An Angular application needs to handle HTTP errors that occur while using the remote service. The errors need to be handled gracefully, rather than crashing the whole page.

The subscribe function on an observable (from the HTTP calls or otherwise) has three handlers: success, error, and subscription complete (see Listing 16-19).
1.    observableInstance.subscribe(
2.        (data) => ({/** success callback */}),
3.        (error) => ({/** error callback */}),
4.        () => ({/** Done */}),
Listing 16-19

Subscribe on an Observable

You have seen success callback functions in the code samples. The second callback function is an error handler. We can use it to handle errors that occur while calling the remote services. Listing 16-20 lines 8 and 9 use a logger service to gracefully handle errors.
1.  getHeroes(): Observable<Array<Superhero>> {
2.    return Observable.create((observer) => {
3.      let results: Array<Superhero> = [];
4.      this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`)
5.        .subscribe(
6.          (data: Array<Superhero>) => ({/** Success handler */}),
7.          (error) => ({
8.            this.errorHandler.log("Error while getting heroes", error);
9.            this.errorHandler.alertFriendlyInfo("Error while getting heroes", observer)
10.          }),
11.          () => ({/** Observable complete */})
12.        );
13.    });
14.  }
Listing 16-20

Observable Handling Errors

Remember, the error handler is on the subscribe function of the observable. Referring back to Listing 16-20, if the observable was returned directly from the Angular service, without a subscribe(), it loses the option to handle errors. If there are multiple calling functions, the code needs to be integrated in all of those places.

To address this scenario, we may pipe an error handling operator with the observable. See line 7 in Listing 16-20. The Angular service function returns the observable without a subscription. As the error occurs, the Angular service still handles the error gracefully.

In Listing 16-21, we log the error information (see line 9). We also throw an error to the calling function, which subscribes to the observable. We continue to do so as the subscriber needs to know that the error occurred, regardless of whether the Angular service logs it or not.
1. import { Observable, Observer, throwError } from 'rxjs';
2. import { catchError } from 'rxjs/operators';
3. // removed code for brevity
4. export class SuperheroDataService {
5.   getHeroes(): Observable<Array<Superhero>> {
6.      return this.httpClient.get<Array<Superhero>>(`${URL_PREFIX}/heroes`)
7.      .pipe(
8.        catchError( (error: HttpErrorResponse) => {
9.           this.errorHandler.log("Error while getting heroes", error);
10.          return throwError("Error while getting heroes");
11.        }));
12.  }
Listing 16-21

Observable Pipes Error Handler

POST Calls with HttpClient

We have seen using GET calls that retrieve a list of superheroes with HttpClient. As mentioned at the beginning of the chapter, use the HTTP POST method to create an entity—a new superhero. Use the POST method on the HttpClient instance to create a new superhero. Consider Listing 16-22.
  createHero(hero: Superhero2){
    return this.httpClient
      .post(`${URL_PREFIX}/heroes`, hero);
      .pipe(
        catchError( (error: HttpErrorResponse) => {
          this.errorHandler.log("Error while creating a hero", error);
          return throwError("Error while creating a hero");
        }));
  }
Listing 16-22

POST Method with HttpClient

The second parameter is the entity being created. In a POST HTTP call, the entity is shared in the body of the HTTP request. Figure 16-4 shows network data for the POST call.
../images/475625_1_En_16_Chapter/475625_1_En_16_Fig4_HTML.jpg
Figure 16-4

POST call on the network

In the Superheroes sample application, update the create superhero form component to invoke the new Angular service function. In Listing 16-23, we inject the service to create an instance (see line 7). The new createHero function is called in line 13.
1. import { SuperheroDataService } from 'src/app/app-http-calls/superhero-data.service';
2. @Component({
3. // removed code for brevity
4. })
5. export class CreateSuperheroComponent implements OnInit {
6.  superhero: Superhero;
7.  constructor(private dataService: SuperheroDataService) {
8.    this.superhero = new Superhero();
9.  }
10.  submitForm(){
11.    let hero = this.superhero
12.    this.dataService
13.      .createHero(hero)
14.      .subscribe(data => console.log(data));
15.  }
16. }
Listing 16-23

Create Superhero Component

Note

The POST call is not made until the observable is subscribed. Just calling createHero on the dataService instance does not invoke the API. In the sample, we subscribe from the component in line 14.

PUT Calls with HttpClient

An HTTP call with the PUT method is very similar to POST, but used to update an entity instead of creating one. The system identifies the entity to be updated with the ID on the entity. On the HTTP call, the ID is specified on the URL. The entity is sent to the remote server in the HTTP request body, similar to POST. In Listing 16-24, we create the URL with the ID of the entity as a parameter (see line 3).
1.  updateHero(hero: Superhero, heroId: string){
2.    return this.httpClient
3.    .put(`${URL_PREFIX}/heroes/${heroId}`, hero)
4.    .pipe(
5.      catchError( (error: HttpErrorResponse) => {
6.        this.errorHandler.log("Error while updating a hero", error);
7.        return throwError("Error while updating a hero");
8.      }));
9.  }
Listing 16-24

Update Entity with HTTP Client

Figure 16-5 shows network data for the PUT call.
../images/475625_1_En_16_Chapter/475625_1_En_16_Fig5_HTML.jpg
Figure 16-5

PUT call on the network

Note

Similar to PUT, PATCH is an HTTP method to update an entity in the system. A RESTful API, while updating an entity, uses PATCH to update a subset of fields without overriding the whole entity, whereas PUT is used to update the entire entity. On the HttpClient instance, we can use the patch function; however, the behavior depends on remote API implementation.

DELETE Calls with HttpClient

Use the HTTP DELETE method to delete an entity from the system. Similar to GET, a DELETE HTTP call does not contain a request body. The system identifies the entity to be deleted with a unique ID. The ID is included in the URL. In Listing 16-25, see line 3. We pass an ID of the hero to be deleted. The service implementation typically selects and marks the entity for deletion in the system.
1.  deleteHero(heroId: string){
2.    return this.httpClient
3.    .delete(`${URL_PREFIX}/heroes/${heroId}`)
4.    .pipe(
5.      catchError( (error: HttpErrorResponse) => {
6.        this.errorHandler.log("Error while deleting a hero", error);
7.        return throwError("Error while deleting a hero");
8.     }));
9. }
Listing 16-25

Delete Entity with HTTP Client

Figure 16-6 shows network data for the DELETE call.
../images/475625_1_En_16_Chapter/475625_1_En_16_Fig6_HTML.jpg
Figure 16-6

DELETE call on the network

Conclusion

Angular provides utilities to request XHR (XMLHttpRequests) calls out of the box with the HttpClient service. It is part of the @angular/common/http module. In this chapter, we started with instructions to include needed dependencies. It is a good practice to separate remote service integration to a new module.

The chapter described using a JSON Server npm package to mock API responses. JSON Server is one of many options to mock API responses. We may use any package or library that helps mimic remote services.

The chapter also explained using the get() method on an HttpClient instance to retrieve data. It makes the HTTP call using the GET method. Use the post() method on an HttpClient instance to create an entity. It makes the HTTP call using POST method. Use the put() or the patch() method on an HttpClient instance to update an entity. It makes the HTTP call using PUT or PATCH, respectively. Use the delete() method on HttpClient instance to delete an entity. It makes the HTTP call using the DELETE method.

Exercise

Create dinosaur data in JSON format. Save the file with a .json extension. Use JSON Server (or any other mocking tool) to serve the data as a remote service.

Integrate the dinosaur list screens using the screens created throughout the book with the remote service. Use GET calls to retrieve data.

Integrate the details screen with a GET call that retrieves data by ID. Use the dinosaur ID specified in browser URL and in the XHR call.

Integrate a reactive form for creating a dinosaur with the remote service. Use the POST call to create a dinosaur entity.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.45.92