Angular HttpClient Deep Dive (Headers, HTTP events, non-JSON data and Interceptors)

Posted by: V Keerti Kotaru , on 7/4/2018, in Category AngularJS
Views: 5405
Abstract: Explore advanced scenarios with the HTTP Client in Angular. In this tutorial, we add headers and parameters to the request, access the response object, work with the HTTPEvent, and look at Interceptors in Angular.

A couple of days ago, I published an article on HTTP Client that described the basics of integrating an Angular application with a server-side API or a Web server.

The tutorial addressed the most common scenarios that developers come across while making a HTTP call from an Angular application. Some of these scenarios were:

- making HTTP calls over GET/POST/PUT/DELETE methods.

- error handling scenarios and

- solutions for separating presentation logic from service API integration details.

To learn more, read the tutorial over here: www.dotnetcurry.com/angularjs/1438/http-client-angular

In this tutorial, we will cover some advanced scenarios. Certain requirements like Security, Logging, Auditing etc., need better flexibility and control on data exchange over HTTP. Often the requirement is overarching and needs a central piece of code addressing it, and this code needs to be unobtrusive and easy to integrate.

Let's begin by looking at ways to access complete HTTP request/response.

Are you keeping up with new developer technologies? Advance your IT career with our Free Developer magazines covering Angular, React, .NET Core, MVC, Azure and more. Subscribe to this magazine for FREE and download all previous, current and upcoming editions.

HTTP Client in Angular - Advanced Scenarios

Add headers to the request

Many a times, web servers need additional headers in the request.

Additional headers could include authorization tokens, content type information, session identifiers etc. HTTP Client provides an easy way to add headers to the request.

Consider the following code which adds an authorization header to a GET HTTP call.

return this.client.get<Traveller>(`${DATA_ACCESS_PREFIX}`,{
     headers: new HttpHeaders({
       "authorization": aUserIdentifier
     })
   })

The GET call has an additional object as a parameter. The object includes field “headers of type HttpHeaders.

We added a new header “authorization” with a value that identifies uniquely for the server. It might perform authorization based on the provided identifier.

Figure 1 shows the network tab on Google Chrome with the additional header in the request.

header-request

Figure 1: Additional header in the request

Add parameters to the request

Adding request parameters is another way to send additional information to the server. Key value pairs can be added to the request to send additional data. It shows as a querystring on the URL and is commonly used with GET calls.

Consider the following code and Figure -2.

return this.client.get<Traveller>(`${DATA_ACCESS_PREFIX}`,{
     params: {
       "travellerId": "1001"
     }
   })

request-param

Figure 2: Adding request parameters

Similar to the code we just saw, the parameter could also be a filter condition. Over here we are providing traveller id to be searched.

Access complete response object

In the previous tutorial, we retrieved typed data objects over the network. The JSON response structure was predefined. The Angular application accessed the data that fit into a predefined TypeScript interface structure.

However, there could be a scenario where we not only need to access response body (JSON data), we also need to access response headers.

Observe Response

HttpClient object allows accessing complete response, including headers. In the browser, response body is a JSON object, which can be copied to a typescript interface or class type.

Response headers are key/value pairs. Consider the following code that accesses complete response object.

--- data-access-Service.ts---
 getTravellersFullResponse():Observable<HttpResponse<Traveller>>{
   return this.client.get<Traveller>(`${DATA_ACCESS_PREFIX}`,{
     observe: "response"
   });
 }

Notice the return type of the function is Observable of generic type HttpResponse, which in-turn has a generic type parameter Traveller. The Traveller object refers to the HTTP response body.

Consider the following code snippet calling the data-access-service and using the response object.

--- traveller-list.component.ts ---

travellers: Array<Traveller> = [];

this.dataAccess.getTravellersFullResponse()
   .subscribe(
     (response) => {
       this.travellers = this.travellers.concat(response.body);
       response.headers.keys().map( (key) => console.log(`${key}: ${response.headers.get(key)}`));
     }
   );

As mentioned in the paragraph above, based on the generic parameter, HttpResponse is expected to return body of type Traveller (HtpResponse<Traveller>).

In the component code above, instead of returned value from the service function, use body to bind to the UI. Notice, travellers, a class level object in the component. This object is available for binding to HTML controls in the template. It is an array of type Traveller.

Headers are key/value pairs. Hence loop through headers and retrieve values based on the key. The sample console logs the headers. Consider Figure 3.

header-log

Figure 3: Header log

Observe HttpEvent

In the prior section, when response was observed, it returned a HttpResponse object. We had access to the whole response, instead of just the body. This approach has more details about the response that is receiving a JSON object.

However, if we need an even better control and need to access individual events or steps while making a HTTP call, observe HttpEvent. It works at a much lower level. It exposes events that capture progress of both the request and response.

Note: In a forthcoming section, all the six events with HttpEvent are listed.

To get started with observing events, consider the following code.

--- data-access-Service.ts---
 getTravellersResponseByEvents(){
   return this.client.get(`${DATA_ACCESS_PREFIX}`,{
     observe: "events"
   });
 }

Notice, the value for observation is events. The getTravellersResponseByEvents returns HttpEvents of generic type object (HttpEvents<object>).

Consider the following code. The subscribe function is called at every stage of the HTTP call. Progress information is much more detailed. On subscribe, the sample has two out of six events. The if condition checks for Http Event Type received. In the sample, we update the status message when a request is sent, a response is received and is ready to use.

--- traveller-list.component.ts ---
this.dataAccess.getTravellersResponseByEvents()
 .subscribe( (result) => {
   if(result.type === HttpEventType.Sent){
     console.log("request sent");
     this.messages.push("request sent"); // update status message
   }else if(result.type === HttpEventType.Response){
     console.log("response obtained");
     this.messages.push("response ready"); // update status message
     this.travellers = this.travellers.concat(result.body as Array<Traveller>) // object is ready to use.
   }
 });

See the following code sninppet for HTML template that shows status messages.

In the TypeScript component code we just saw, the messages object holds status messages. At every stage the array is pushed with new messages. The template also shows list of travellers. This list is updated to an object named travellers (in the component). It is done once the result type is HttpEventType.Response.

--- traveller-list.component.html ---
<div *ngFor="let msg of messages">
 <div> {{ msg }}</div>
</div>
<hr />
<div *ngFor="let traveller of travellers">
 <div> {{traveller.lastName}}, {{traveller.firstName}} is {{traveller.age}} years old.</div>
 <div> City: {{traveller.city}}. Country: {{traveller.country}}</div>
 <div><button (click)="deleteTraveller(traveller.id)">Delete</button></div>
 <br />
</div>

See Figure 4 for the result.

status-messages

Figure 4: Status Messages

Following is the complete list of HTTP event types available.

  1. HttpEventType.Sent (numeration value 0) - Request sent
  2. HttpEventType.UploadProgress (Enumeration value 1) - Request upload in progress
  3. HttpEventType.ResponseHeader (Enumeration value 2) - Response headers received
  4. HttpEventType.DownloadProgress (Enumeration value 3) - Response download in progress
  5. HttpEventType.Response (Enumeration value 4) - Response ready to use
  6. HttpEventType.User (Enumeration value 5) - For a custom event.

Show non-JSON data

There are always scenarios to download files, BLOB (binary large object) or text from the remote server. An Angular application needs to support retrieving such content.

Consider the following sample that retrieves text content and shows it on the page. Notice responseType is set as text, anticipating text response (instead of JSON).

--- data-access-Service.ts---
 getText(){
   return this.client.get(`${BLOBL_ACCESS_PREFIX}/1.txt`,{
     responseType: "text"
   });
 }

A Component calls and consumes the text response. It sets in on a string variable and shows it on the template.

--- show-text.component.ts ---
 downloadText(){
   this.dataAccess.getText()
   .subscribe((data) => {
     this.textContent = data;
   }, () => console.log("error"));
 }
--- show-text.component.html ---

<button (click)="downloadText()">Download Text </button>
<div>{{textContent}}</div>

We may also download a PDF document with a similar approach. Consider the following code where we will use responseType blob instead of text.

--- data-access-Service.ts---
 getDocument(){
   return this.client.get(`${BLOBL_ACCESS_PREFIX}/1.pdf`,{
     responseType: "blob"
   });
 }

The component will use the data slightly different. Will use the BLOB returned from the service and open it on the window to show the file/content.

--- show-pdf.component.ts ---
 downloadDocument(){
   this.dataAccess.getDocument()
   .subscribe((data) => {
     console.log(data);
     let url = window.URL.createObjectURL(data);
     window.open(url); // The downloaded file is open with the help of a browser window.
   }, () => console.log("error"));
 }

Interceptors

UI applications commonly need altering of request and response before it’s sent on the wire and right after receiving. It could be for various reasons including logging request/responses, adding common authorization headers that the server might need, caching and so on.

Such activities are preferably carried out at a central location. Interceptors are perfect for this requirement.

In Angular, a series of interceptors can be created, which pass the request from one interceptor to the other. They are ordered and the request is passed in sequence. However, response is received in exact reverse order to the requests.

For example, consider the following interceptor

  1. Caching Interceptor – stores certain details from a response in local storage or session/storage.
  2. Authorization Interceptor - Adds required header to all requests matching a pattern.
  3. Logging Interceptor - Logs data certain type of data being exchanged.

Let us consider that the Caching Interceptor is run first. It does required processing on the request and invokes authorization interceptor if the request needs to go to the server.

Say authorization headers are added and passes the request for logging. The logging interceptor processes it and allows the actual network call. When the response is received, it is first received by the Logging Interceptor, which passes it to Authorization Interceptor, which passes it to the Cache Interceptor that finally sends it back to the UI application and components.

angular-interceptors

Figure 5: Interceptors in action

Create the first interceptor

To create an interceptor, create a service that implements HttpInterceptor (in module @angular/common/http). To create a service with Angular CLI, run the following command,

ng g service services/logging-interceptor

Import and implement the HttpInterceptor. The interface needs a definition for the function intercept. Consider the following code.

The interceptor doesn’t do anything useful yet. It just logs a static message to the browser console.

@Injectable()
export class LoggingInterceptorService implements HttpInterceptor {

 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
   console.log("request/response intercepted");
   return next.handle(req);
 }
  
 constructor() { }

}

The Request is passed in as a parameter to the interceptor (or the intercept function). This is how interceptor has access to the request. Considering the function intercept returns an Observable, we can access the response every time the observable is resolved or errored out.

Notice, the intercept function returns an Observable of generic type HttpEvent. That means, interceptor has access to all the six events that occur with a HTTP call. Refer to the Observe Http Event section above for details on the events.

After an interceptor does its job with the request, it needs to call the handle function next (which is an object HttpHandler). This process invokes next interceptor in the chain.

Provide the Interceptor

Interceptor needs to be provided before the service making the Http call. The Injector uses all interceptors that are provided at the time of injecting a service (that makes HTTP calls).

If an interceptor is provided after the service that uses HttpClient instance, it would be ignored.

Note: Providing a service makes it available for the injector. A service is added to providers array while creating an Angular module or a component. It can be provided with a token similar to the following example. If provided without a token, it will be auto created.

In this sample, let’s provide the interceptor in the AppModule. Begin by importing token for the interceptor HTTP_INTERCEPTORS ( from @angular/common/http). Import the service LoggingInterceptor as well.

Provide the interceptor as shown the following sample:

@NgModule({
 declarations: [
   ...
 ],
 imports: [
  ...
 ],
 providers: [
   DataAccessService,
   {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptorService, multi:true}
  
 ],
 bootstrap: [AppComponent]
})
export class AppModule { }

Please note, the field multi indicates whether or not the token being provided can be used for multiple services. With the value true, even though in this example we are just using one service, we can provide many services with the same token. See figure-6 for the result as we make HTTP calls.

http-calls

Figure 6: HTTP Calls

Access response in interceptor

As mentioned above, the intercept function returns an Observable of type HttpEvent. Once subscribed (in a function that consumes response - do not subscribe in interceptor), the Http call is invoked. We can use the operator tap, imported from rxjs/operators to access the success or failure response.

Consider the following code:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
   console.log("request/response intercepted");
   return next.handle(req)
     .pipe(
       tap(
         (result: HttpEvent<any>) => console.log("response intercepted", result),
         (error: HttpErrorResponse) => console.error("error response intercepted", error)
       )
     );
 }

Please note, the parameter to the success function is of type HttpEvent of a generic type. And for the error handler, the parameter is of type HttpErrorResponse.

Figure-7 shows success and error response.

success-error-response

Figure 7: Success Error Response

Conclusion

Most Angular applications’ server-side API integration can be achieved with basic HttpClient API.

The service is ready-made and provides an easy way to integrate with RESTful services. However, there are times where the developer needs deeper control while making HTTP calls. Concepts discussed in this article are intended to address such scenarios.

We began by providing the ability to access request and response headers. It allows developers to customize the HTTP call. We later addressed writing such custom code in a reusable manner.

By creating an interceptor, we can write code that accesses and modifies headers, massages data from a central location. Without interceptors, developers would need to modify each service function to do custom editing/reading additional details.

References

Angular documentation for HttpClient -https://angular.io/guide/http

This article was technically reviewed by Ravi Kiran.

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
V Keerti Kotaru has been working on web applications for over 15 years now. He started his career as an ASP.Net, C# developer. Recently, he has been designing and developing web and mobile apps using JavaScript technologies. Keerti is also a Microsoft MVP, author of a book titled 'Material Design Implementation using AngularJS' and one of the organisers for vibrant ngHyderabad (AngularJS Hyderabad) Meetup group. His developer community activities involve speaking for CSI, GDG and ngHyderabad.

Subscribe to V Keerti Kotaru's thoughts on Twitter @keertikotaru or at LinkedIn.



Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

FREE .NET MAGAZINES

Free DNC .NET Magazine

Tags

JQUERY COOKBOOK

jQuery CookBook