Application configuration
Angular HTTP RxJS SPA Typescript Web API

How to Configure an Angular Application for HTTP Client Web API Requests

Welcome to today’s post.

In today’s post I will be providing an overview of how to configure and integrate HTTP Web API requests within an Angular application.

In a previous post I showed how to call Web API services from an Angular application. In this post, I will be making calls to Web API services, but with some differences in the way the Angular application is configured. I will show how to make the architecture usable across multiple build environments, the Web API base URI configurable, and the application configuration injectable into Angular API service classes.

In addition to the above-mentioned configurations, I will go through the following steps to achieve our configuration and integration goals:

  1. Installation and configuration of the Angular HTTP client library  
  2. Configuration of HTTP REST client calls to a Web API
  3. Using a TypeScript DTO class for Web API calls
  4. Execution of Web API requests
  5. Handling Error Responses from a Web API HTTP REST call

Using the Angular HTTP Client Library

In this section I will be explaining what the Angular HTTP client library is and how to install and configure it within an Angular application.

The Angular HTTP client library allows our Angular client application components to integrate with HTTP REST Web API services.  

The common HTTP REST requests are:

GET

POST

PUT

DELETE

The Angular web framework can access HTTP requests using the HttpClientModule.  

When we create an Angular application the @angular/common library is installed into the node_modules folder and contains the http modules. 

The package.json will contain a reference to the installed NPM package for the @angular/commonhttp library:

"dependencies": {
    …
    "@angular/common": "~9.1.13",
    …

For the http client library to be able to be utilised by our Angular application components we must import the library into the application module, app.module.ts as shown:

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

We must also include the http client module HttpClientModule within the imports array as shown:

@NgModule({
    declarations: [
        …
    ],
    imports: [
        …
        HttpClientModule,
        …
    ],
    providers: [
        ApiService, 
        {
            provide: APP_CONFIG, 
            useValue: BOOKS_DI_CONFIG
        },
        AuthService,
        {
            provide: HTTP_INTERCEPTORS,
            useClass: AuthInterceptor,
            multi: true
        }
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

In the next section, I will show how to use a centralized configuration file and re-use them with injection into API service classes.

Configuration of HTTP Client REST Calls to a Web API

To make a Web API HTTP client REST request, we can either call the Web API from our components or decouple the request through an Angular API service component. Decoupling the HTTP request through an API service class is the preferable method as it not only refactors our HTTP requests, but also satisfy the principles of testability for our API services and components.  

Below is an excerpt of an API service class that makes an HTTP GET request to a Web API method:

import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
import { AppConfig, APP_CONFIG } from '../app.config';
…

@Injectable()
export class ApiService {
    private baseAPI: string = environment.baseApiUrl;
    private baseLoanAPI: string = environment.baseLoanApiUrl;
    private baseIdentityAPI: string = environment.baseIdentityApiUrl;

    constructor(@Inject(APP_CONFIG) private config: AppConfig, private http: HttpClient) {}
    getUserRoles(userName): Observable<string[]> {
        return this.http.get<string[]>(this.config.baseIdentityApiUrl + '/ManageUser/GetUserRoles/' + encodeURIComponent(userName)); 
    }
    …
}

Our environment configuration files will contain the following constants for our Web API URIs:

export const environment = {
    production: false,
    baseApiUrl: "http://localhost/BookLoan.Catalog.API/api",
    baseLoanApiUrl: "http://localhost/BookLoan.Loan.API/api",
    baseIdentityApiUrl: "http://localhost/BookLoan.Identity.API/api"
};

In the next section, I will show how to cleanly encapsulate input parameters used to call Web API methods by using DTO classes that can be included in Angular API Service class methods.  

Using a Typescript Class DTO for Web API Requests

To be able to pass input to or receive output from HTTP requests we should encapsulate the parameters using a Data Transfer Object (DTO). A DTO is very similar to a model class that is used within a server-side Web API that is written in C#. By imposing concrete types on our parameters instead of the Any type, we can catch difficult to detect type mismatch related errors we might get during development. 

An example of how we would use a DTO to transfer response output from an HTTP GET request is shown below with the following API call:

getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.config.baseApiUrl + '/Book/List'); 
}

An example of how we would use a DTO to transfer input into an HTTP POST request is shown below with the following API call:

saveBook(book) 
{
    return this.http.post<Book>(this.config.baseApiUrl + '/Book/Create', book);
}

An equivalent DTO Typescript class that provides the input and output data for the above HTTP GET and POST requests is as follows:

export class Book
{
    public id: number;
    public title: string;
    public author: string;
    public yearPublished: number;
    public genre: string;
    public edition: string;
    public isbn: string;
    public location: string;
    public dateCreated: Date;
    public dateUpdated: Date;
}

In the next section, I will show how to use the Angular HTTP client library to make Web API calls with typed request and response parameters.

Executing Web API Requests

In this section I will go into more detail how we subscribe to an HTTP request.

By default, the response we get back from an HTTP GET request is of type JSON.

We could also subscribe to a Web API method that returns a different response type, such as Text or Blob for a file download request.

After calling a http.get() request, an Observable type is returned. However, this does not give us any data. We still have to call the Subscribe() method of the observable to initiate the request on the server side API method. Once the API method completes execution, a response is returned to the calling Angular client.  

The following is an example of how we use Subscribe() to execute and retrieve a response from the HTTP GET request:

this.http.get<Book[]>(this.config.baseApiUrl + '/Book/List')
    .subscribe(res => 
        this.data = res; 
    )

In the above call, notice the response from the API call is of the Book class, which is an encapsulation of book properties.

If we inject our Angular API service into our components, it is recommended we subscribe to the HTTP client observable request within the component and not within the API service class. In some components, we may want to synchronize multiple API service requests and combine them within the component using an RxJS operator to combine the observable data streams.

An example of observable data synchronization within an Angular component is discussed in one of my previous posts.

Our API service method would return a typed Observable like this:

getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(
        this.config.baseApiUrl + '/Book/List'
    ); 
}

And the component would subscribe to the API service method like this:

this.api.getBook(this.id).subscribe((res: Book) => {
    this.book = res; 
    … 
});

In the next section, I will show how to handle errors that come from calling Web API calls.

Handling Error Responses from a Web API HTTP REST Call

When handling the result from a subscribed Observable, there are three operators that can be applied to the result depending on the status of the data. These are next(), error(), and complete(). The syntax below is how we can deal with these states after a subscribe() is applied to the observable:

observable.subscribe({
    next(x) { console.log('retrieved the value ' + x); },
    error(err) { console.error('and error has occurred: ' + err); },
    complete() { console.log('request done'); }
});

With the observable operators, we can amend our error handler for a subscribed observable as shown:

this.api.getBook(this.id).subscribe((res: Book) => {
    this.book = res; 
    … 
},
error => {
    console.error('There was an error!', error);
});

We can use an alternative way of handling the error before the observable is subscribed, and that is to use the catchError() RxJS operator within a pipe() method.

To make use of these operators, we import the following RxJS libraries:

import { Observable, throwError } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';

The catchError() operator is called when the observable goes into error, and the finalize() operator is called when the observable completes:

getBooks(): Observable<Book[]> {
return this.http.get<Book[]>(this.config.baseApiUrl + '/Book/List')
    .pipe(
        catchError(err => 
        {
            if (err.status === 0) {
                // A client-side or network error occurred.
                console.error('An error has occurred:', err.error);
            } 
            else 
            {
                // A backend returns an unsuccessful response code.
                console.error(
                    `Backend has returned code 
                    ${err.status}, the body was: `, 
                    err.error
 				);
            }
            // Returns observable with user-friendly error message.
            return throwError(
                'An error occurred; please re-try the request.'
            );
        }),
        tap(() => 
            console.log('executed call to getBook().')
        ),
        finalize(() => {
            console.log("completed call to getBook()")
        })
    );
}

With some refactoring, we can move the error handling into a separate method:

private handleError(error: HttpErrorResponse) {
    if (error.status === 0) {
        // A client-side or network error occurred.
        console.error('An error has occurred:', error.error);
    } else {
        // A backend returns an unsuccessful response code.
        console.error(
            `Backend has returned code ${error.status}, 
            the body was: `, 
            error.error
        );
    }
    // Returns observable with user-friendly error message.
    return throwError(
        'An error occurred; please re-try the request.');
}

And the HTTP client call within the API service method is tidied up as shown:

getBooks(): Observable<Book[]> {
    return this.http.get<Book[]>(this.config.baseApiUrl + '/Book/List')
        .pipe(
            catchError(err => this.handleError(err)),
            tap(() => 
                console.log('executed call to getBook().')
            ),	
            finalize(() => {
                console.log("completed call to getBook()")
            })
        ); 
}

The error handling can be undertaken within an Angular API service method (before subscribing) or within the Angular component level (after subscribing). This will depend on whether you are calling one API service or combining multiple API services. By decoupling the error handler from the HTTP request, we can test error handling in isolation of the Angular component.  

We have learnt how to configure Web API HTTP Client requests within an Angular application with the following tasks:

  1. Setting up the Angular HTTP client library.
  2. Environment configuration.
  3. Setting up DTO classes.
  4. Executing HTTP requests.
  5. Handling error responses.

Further details on the RxJS Observable can be obtained on the RXJS site.

Further details on calling a Web API with the Angular HTTP client library can be obtained on the Angular developer site.

That is all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial