Xử lý HTTP interceptor trong Angular 2

Interceptor là gì

HTTP Service của angular 2 cho phép chúng ta giao tiếp với backend bằng các thực hiện các HTTP Request. Có nhiều trường hợp chúng ta cần thiết lập các thông số cố định chẳng hạn như header, body... hoặc điều khiển các request trước khi gửi đến server hoặc xử lý các phản hồi từ máy chủ trước khi thông báo thực hiện xong request. Việc xử lý lỗi cũng là một ví dụ hay cho http interceptor

Bây giờ chúng ta thử xem nếu không có HTTP Interceptor thì sẽ phải xử lý thế nào?

Một ứng dụng sử dụng RESTful API để thực hiện các yêu cầu lên máy chủ web, trong trường hơp này mình sử dụng authorization key để xác thực đăng nhập. Sau khi đăng nhập thành công, server sẽ gửi 1 đoạn mã token để xác minh cho các yêu cầu lên máy chủ về sau, mỗi lần request lên server mình phải thêm token này vào trong header. Bạn để ý đoạn header "authorization: Bearer: xxxx" chính là chỗ mình phải gắn header để xác minh request này là chính chủ từ người dùng đã đăng nhập vào trước đo. Nếu authorization key sai hoặc thiếu đồng nghĩa với việc server sẽ trả về mã lỗi báo chưa đăng nhập (401). Nếu mỗi lần thực hiện các request mà phải gắn thêm header vào thì đúng là khá mất thời gian cho việc viết code và rất khó nâng cấp, thay đổi cũng như bảo trì. Vậy có cách nào để chỉ phải cấu hình 1 chỗ mà mọi request được có các tham số giống nhau? câu trả lời là interceptor ra đời để giải quyết bài toán đó.

Triển khai code

Trong ví dụ này mình sử dụng JWT(Json web token) để xác minh đăng nhập. Đầu tiên chúng ta phải tạo 1 service để lấy token và kiểm tra token. Token được lưu ở localStorage. Mình đặt tên là auth.service.ts

import { Injectable } from '@angular/core';
import decode from 'jwt-decode';
import { JwtHelperService } from '@auth0/angular-jwt';
import { HttpRequest } from '@angular/common/http/src/request';

@Injectable()
export class AuthService {
  cachedRequests: Array<HttpRequest<any>> = [];
  public getToken(): string {
    return localStorage.getItem('jwtToken');
  }

  public isAuthenticated(): boolean {
    // get the token
    const token = this.getToken();
    // return a boolean reflecting 
    // whether or not the token is expired
    return this.tokenNotExpired(token);
  }
  public tokenNotExpired(token) {
    if(token) {
      var jwtHelper = new JwtHelperService();
      return token != null && !jwtHelper.isTokenExpired(token);
    }else {
      return false;
    }
    
  }
  public collectFailedRequest(request): void {
    this.cachedRequests.push(request);
  }

  public retryFailedRequests(): void {
    // retry the requests. this method can
    // be called after the token is refreshed
  }
}

Sau đó mình xử lý tiếp việc gán JWT và header bằng cách tạo ra interceptor: token.interceptor.ts

Interceptor trước khi request

Interceptor này sẽ cấu hình request trước khi gửi đến server.

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  public static currentTeamId : any;
  constructor(public auth: AuthService) {
   }

   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
    let requestOption:any = {};
     
    if(this.auth.getToken()) {
      requestOption.setHeaders = {
        Authorization: `Bearer ${this.auth.getToken()}`
      }
    } 

    request = request.clone(requestOption); 
    return next.handle(request)
  }
}

Class này implement từ lớp HttpInterceptor để viết lại hàm intercept trong mặc định. Bạn để ý thấy mình sử dụng hàm setHeaders để thêm 1 header vào request, việc này được thực hiện trước khi gửi yêu cầu lên server. Ngoài ra nếu muốn thêm tham số bạn có thể sử dụng setParams để thêm tham số vào request.

Interceptor sau khi request

Interceptor này sẽ xử lý sau khi request và nhận phản hồi từ server. Trường hợp này thường được sử dụng để xử lý lỗi trước khi trả về cho client. Tương tự như token interceptor. Ở đây mình tạo file jwt.interceptor.ts để xử lý

import { Injectable } from '@angular/core';

import 'rxjs/add/operator/do';
import { AuthService } from './auth.service';
import {
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpInterceptor
  } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { HttpResponse, HttpErrorResponse } from '@angular/common/http/';
import { Router } from '@angular/router';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  constructor(
    public auth: AuthService,
    private router: Router
  ) {}
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
    return next.handle(request).do((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        // do stuff with response if you want
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        if (err.status === 401) {
          // redirect to the login route
          // or show a modal
          
          this.router.navigate(['auth']);

        }
      }
    });
  }
}

Bạn để ý trong này có đoạn if (err.status === 401) . Đây sẽ là chỗ để bạn xử lý các lỗi sau khi request và nhận phản hồi về. Trường hợp này mình đang xử lý nếu server trả về mã lỗi 401(Lỗi chưa đăng nhập).

Bước sau cùng: Khai báo trong app.module

Bạn phải khai báo các Interceptor sẽ sử dụng các class nào để xử lý. Bạn thêm vào providers trong app.module

providers: [
    {
      provide: LocationStrategy,
      useClass: HashLocationStrategy
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: JwtInterceptor,
      multi: true
    },
    ......

Vậy là chúng ta đã xử lý xong nhiệm vụ

Tổng kết

Trong bài viết này mình đã hướng dẫn và giải thích cho các bạn về angular interceptors. Mình cũng đưa ra ví dụ để các bạn có thể sử dụng trong project của mình. Mình hi vọng các bạn có thể thực hiện được và hãy gửi thắc mắc/lỗi để chúng ta cùng xử lý. Cảm ơn! Quy Nguyen