Angular 2 Custom Attribute To Secure The HTTP Requests And To Show/Hide Spinner During The Service Call

In this article, I am going to show you the real time uses of Custom Attribute. I have used Custom Attributes in 2 places, and they are,

  • To secure the HTTP requests
  • Show/Hide spinner during the service call

To secure the HTTP requests

Let say we have a requirement to validate every HTTP request before processing the request. The best solution I can think of is to extend the Http class and make a custom Http provider which automatically adds the authentication token to every http request. I understand that Angular2 is having multiple providers for ConnectionBackend, they are XHRBackend and JSONPBackend_. Here I am going to use XHRBackend from @angular/http module.

Create Custom Http Class

First, let's create our own custom Http provider class by extending Http class and then write our own business logic as per the need. If you observe the below code, I have created a class by extending Http class and named it as "HttpService". Inside the constructor I have configured the token to the request options and then called the Http class's constructor with the keyword super by passing the configured request options as an input parameter to the Http class's constructor.

In the same way, we can create our own HTTP requests (Get, Post, etc.) as per the required business logic and then inside the HTTP requests, call the base class's method by using super keyword. In the below code snippet, I have created a method called "get" and applied the token to both "url" and "RequestOptionsArgs" as per my need and then called the base class's "get" method. Similarly you can create your own HTTP request methods

http.interceptor.ts

  1. import { Injectable } from '@angular/core';  
  2. import {  
  3.     Http,  
  4.     ConnectionBackend,  
  5.     RequestOptions,  
  6.     RequestOptionsArgs,  
  7.     Response  
  8. } from '@angular/http';  
  9. import { Observable } from 'rxjs/rx';  
  10.   
  11. @Injectable()  
  12. export class HttpService extends Http {  
  13.     constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {  
  14.         let _token = localStorage.getItem('auth_token'); // get the custom token from the local storage  
  15.         defaultOptions.headers.set('Authorization''Bearer ${_token}');  
  16.         super(backend, defaultOptions);  
  17.     }  
  18.   
  19.     get(url: string, optionsArgs?: RequestOptionsArgs): Observable<Response> {  
  20.         console.log("Interceptor: ", url, optionsArgs);  
  21. //let ¬_token = localStorage.getItem('auth_token');  
  22. //if (typeof url === "string") { // this means we have to add the token to the options not in url  
  23. //if (!optionsArgs) {  
  24. // let's make option object  
  25. //optionsArgs = {headers: new Headers()};  
  26. //}  
  27. //optionsArgs.headers.set('Authorization', `Bearer ${_token}`);  
  28. //}   
  29. //else {  
  30. // we have to add the token to the url object  
  31. //url.headers.set('Authorization', `Bearer ${_token}`);  
  32. //}  
  33.         return super.get(url, optionsArgs).catch(this.catchException(this));  
  34.     }  
  35.     private catchException (self: HttpService) {  
  36.     return (resp: Response) => {  
  37.       console.log(resp);  
  38.       if (resp.status === 401 || resp.status === 403) {  
  39.         // user not authenticated  
  40.         console.log(resp);  
  41.       }  
  42.       return Observable.throw(resp);  
  43.     };  
  44.   }  
  45. }  

Configure the Custom Http Class

We have learned the creation of custom Http class. To use the created custom Http class, now we have to configure our main module to provide the XHRBackend to our own custom Http class. In your main module declaration, add the following to the providers array:

app.module.ts

  1. import { NgModule } from '@angular/core';  
  2. import { HttpModule, XHRBackend, RequestOptions } from '@angular/http';  
  3. import { HttpService } from './http.interceptor';  
  4. -------------  
  5. -------------  
  6. -------------  
  7. @NgModule({  
  8.   imports: [......],  
  9.   declarations: [......],  
  10.   bootstrap: [AppComponent],  
  11.   providers: [UserService, AuthService, RouteGaurd, NewUserGaurd,  
  12.     {  
  13.       provide: HttpService,  
  14.       useFactory: function httpServiceFactory(backend: XHRBackend,  
  15.        defaultOptions: RequestOptions) {  
  16.         return new HttpService(backend, defaultOptions);  
  17.       },  
  18.       deps: [XHRBackend, RequestOptions]  
  19.     }  
  20.   ]  
  21. })  
  22. export class AppModule { }  

To use the custom Http class, import the custom Http class and then inject in the constructor as a parameter. After that, we can now use our custom Http provider in our services. For example,

user.service.ts

  1. import { Injectable } from '@angular/core';  
  2. import { HttpService } from '../http.interceptor';  
  3. import { Observable } from 'rxjs/Observable';  
  4. import 'rxjs/Rx';  
  5.   
  6. @Injectable()  
  7. export class UserService {  
  8.     private _serviceURL = 'http://jsonplaceholder.typicode.com/';  
  9.     private users: any[] = [];  
  10.     count = 0;  
  11.     constructor(private _http: HttpService) { }  
  12.   
  13.     getUsers(): Observable<any> {  
  14.         this.count += 1;  
  15.         console.log(this.count);  
  16.   
  17.         let url = this._serviceURL + 'users';  
  18.         return this._http.get(url)  
  19.             .map((response) => response.json())  
  20.             .catch((error) => {  
  21.                 console.log("User Service - Error");  
  22.                 return Observable.throw(error || 'Server error: User Service failed to retrieve information.')  
  23.             });  
  24.     }  
  25. }  

Output

After executing the above code, observe the below screenshot, our custom Http class called and log the message in browser console,


Show/Hide spinner during the service call

For this example, I am not going to discuss each block of code because the below code snippets are written in the same way which I have shown you for the above example:

preloader-service.ts

  1. import { Injectable } from '@angular/core';  
  2.   
  3. @Injectable()  
  4. export class PreloaderService {  
  5.   public static fullLoadingCount: number = 0;  
  6.   public static smallLoadingCount: number = 0;  
  7.   
  8.   getPreloaderCount(preloaderType = 'full'): number {  
  9.     if (preloaderType === 'full') {  
  10.       return PreloaderService.fullLoadingCount;  
  11.     } else if (preloaderType === 'small') {  
  12.       return PreloaderService.smallLoadingCount;  
  13.     }  
  14.   }  
  15.   
  16.   showPreloader(preloaderType = 'full'): void {  
  17.     if (preloaderType === 'full') {  
  18.       PreloaderService.fullLoadingCount++;  
  19.     } else if (preloaderType === 'small') {  
  20.       PreloaderService.smallLoadingCount++;  
  21.     }  
  22.   }  
  23.   
  24.   hidePreloader(preloaderType = 'full'): void {  
  25.     if (preloaderType === 'full') {  
  26.       PreloaderService.fullLoadingCount--;  
  27.     } else if (preloaderType === 'small') {  
  28.       PreloaderService.smallLoadingCount--;  
  29.     }  
  30.   }  
  31. }  

http-service.ts

  1. import { Injectable } from '@angular/core';  
  2. import { Http, ConnectionBackend, Request, RequestOptions, RequestOptionsArgs, Response, Headers } from '@angular/http';  
  3. import { Observable } from 'rxjs/Observable';  
  4. import 'rxjs/add/observable/throw';  
  5. import 'rxjs/Rx';  
  6. import { PreloaderService } from './preloader-service';  
  7. import { Config } from '../config';  
  8.   
  9. @Injectable()  
  10. export class HttpService extends Http {  
  11.   
  12.   constructor(backend: ConnectionBackend,  defaultOptions: RequestOptions,  
  13.               private preloaderService: PreloaderService) {  
  14.     super(backend, defaultOptions);  
  15.   }  
  16.   
  17.   /** 
  18.    * Performs any type of http request. 
  19.    * @param url 
  20.    * @param options 
  21.    * @returns {Observable<Response>} 
  22.    */  
  23.   request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {  
  24.     return super.request(url, options);  
  25.   }  
  26.   
  27.   /** 
  28.    * Execute a request with Get Http method. 
  29.    * @param url 
  30.    * @param options 
  31.    * @param preloaderType 
  32.    * @returns {Observable<>} 
  33.    */  
  34.   get(url: string, options?: RequestOptionsArgs, preloaderType?: string): Observable<any> {  
  35.     this.requestInterceptor(preloaderType);  
  36.     let fullUrl = this.getFullUrl(url);  
  37.   
  38.     return super.get(fullUrl, this.requestOptions(options))  
  39.       .catch(this.onCatch)  
  40.       .do((res: Response) => {  
  41.         this.onSubscribeSuccess(res);  
  42.       }, (error: any) => {  
  43.         this.onSubscribeError(error);  
  44.       })  
  45.       .finally(() => {  
  46.         this.onFinally(preloaderType);  
  47.       });  
  48.   }  
  49.   
  50. /** 
  51.    * Build full URL for request. 
  52.    * @param str 
  53.    * @returns {string} 
  54.    */  
  55.   private getFullUrl(str): string {  
  56.     return Config.api + str;  
  57.   }  
  58.   
  59.   /** 
  60.    * Request interceptor. 
  61.    */  
  62.   private requestInterceptor(preloaderType = 'full'): void {  
  63.     this.preloaderService.showPreloader(preloaderType);  
  64.   }  
  65.   
  66.   /** 
  67.    * Response interceptor. 
  68.    */  
  69.   private responseInterceptor(preloaderType = 'full'): void {  
  70.       this.preloaderService.hidePreloader(preloaderType);  
  71.   }  
  72.   /** 
  73.    * Error handler. 
  74.    * @param error 
  75.    * @param caught 
  76.    * @returns {ErrorObservable} 
  77.    */  
  78.   private onCatch(error: any): Observable<any> {  
  79.     console.log('onCatch');  
  80.     return Observable.throw(error);  
  81.   }  
  82.   
  83.  /** 
  84.    * onFinally 
  85.    */  
  86.   private onFinally(preloaderType = 'full'): void {  
  87.     this.responseInterceptor(preloaderType);  
  88.   }  
  89. }  

preloader-small.ts

  1. import { Component } from '@angular/core';  
  2. import { PreloaderService } from '../../services/preloader-service';  
  3.   
  4. @Component({  
  5.   selector: 'preloader-small',  
  6.   styleUrls: [  
  7.     './preloader-small.scss'  
  8.   ],  
  9.   templateUrl: './preloader-small.html'  
  10. })  
  11. export class PreloaderSmall {  
  12.   constructor(public preloaderService: PreloaderService) {  
  13.   }  
  14. }  

 

preloader-small.html

  1. <div class="preloader-small"  
  2.      *ngIf="preloaderService.getPreloaderCount('small') > 0 && preloaderService.getPreloaderCount('full') == 0"  
  3. ></div>  

preloader-full.ts

  1. import { Component } from '@angular/core';  
  2. import { PreloaderService } from '../../services/preloader-service';  
  3.   
  4. @Component({  
  5.   selector: 'preloader-full',  
  6.   styleUrls: [  
  7.     './preloader-full.scss'  
  8.   ],  
  9.   templateUrl: './preloader-full.html'  
  10. })  
  11. export class PreloaderFull {  
  12.   constructor(public preloaderService: PreloaderService) {  
  13.   }  
  14. }  

preloader-full.html

  1. <div class="preloader-full"  
  2.      *ngIf="preloaderService.getPreloaderCount('full') > 0">  
  3. </div>  

home.component.ts

  1. import { Component } from '@angular/core';  
  2. import { PostService } from '../../services/post-service';  
  3.   
  4. @Component({  
  5.   selector: 'home',  
  6.   templateUrl: './home.component.html'  
  7. })  
  8. export class HomeComponent {  
  9.   public posts = [];  
  10.   
  11.   constructor(private postService: PostService) {  
  12.     this.postService.getPosts('full');  
  13.   }  
  14.   
  15.   ngOnInit() {  
  16.     this.postService.posts.subscribe(data => {  
  17.       if (data) {  
  18.         this.posts = _.concat(this.posts, data);  
  19.       }  
  20.     });  
  21.   }  
  22.   
  23.   getPosts(preloaderType) {  
  24.     this.postService.getPosts(preloaderType);  
  25.   }  
  26. }  

home.component.html

  1. <h2>Latest posts</h2>  
  2.   
  3. <div *ngIf="posts">  
  4.   <p *ngFor="let post of posts;">{{post.title}}</p>  
  5. </div>  
  6.   
  7. <preloader-small></preloader-small>  
  8.   
  9. <button class="courses__load-more"  
  10.         (click)="getPosts('small')">  
  11.   Load with small preloader  
  12. </button>  
  13.   
  14. <button class="courses__load-more"  
  15.         (click)="getPosts('full')">  
  16.   Load with full preloader  
  17. </button>  

Output

Once the page loads, when we click on "Load with small preloader" button,

output

Below is the output when we click on "Load with full preloader" button,

output

I have uploaded the entire code in zip format, you can the download the code and play around with it.

Thanks for reading.