MurabitoB

Angular 初探 HttpInterceptor

N 人看过

前言

雖然內建的 HttpClient 本身就可以針對 Http 請求進行設定 header, queryString, body 等參數上的設定。

但是,如果今天的情境是請求要呼叫的 API 需要帶上一些 token 放在 header 或者其他地方的話,在每隻都要主動填入的話會造成一直在寫重複的 Code。

如果今天的情境是在每次呼叫特定的 API 的話都要都要帶上特定的參數的話,這時候就很適合用 HttpInterceptor 來進行處理。

HttpInterceptor 可以針對每次每次的 Request Response 進行加工,使處理的邏輯能夠集中在一個地方,概念上有點像後端 framework 的 Middleware

實作

官網的介面定義如下

interface HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>>;
}

HttpInterceptor 本身是個 Service ,可以透過 intercept 這個函數對請求進行處理。

透過以下方式複製一份新的 request 並添加 Authorization 的 Header

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const loginInfo = this.authService.loginInfo$.getValue();
    if (loginInfo)
      req = req.clone({
        headers: req.headers.append("Authorization", `Bearer ${loginInfo}`),
      });

    return next.handle(req);
  }
}

接著,將其放在 AppModule,要注意 multi 要設成 true

理由可以參照這篇: angular - what does the multi: true attribute of HTTP_INTERCEPTORS mean? - Stack Overflow

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
})

此外 HttpInterceptor 可以同時引用多組

@NgModule({
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AInterceptor,
      multi: true
    },
	{
      provide: HTTP_INTERCEPTORS,
      useClass: BInterceptor,
      multi: true
    }
  ],
})

在下列的情境下在請求發送後會依照由上到下的順序經過 A => B

處理 Response

HttpInterceptor 除了處理 Request 之外,也可以在原本的 handle 使用 pipe 來處理 Response,在實務上可以用來處理一些請求回傳的錯誤,例如得到 401 unauthorized 後跳回登入頁。

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    const loginInfo = this.authService.loginInfo$.getValue();
    if (loginInfo)
      req = req.clone({
        headers: req.headers.append("Authorization", `Bearer ${loginInfo}`),
      });

    return next.handle(req).pipe(
      catchError((x) => {
        if (x instanceof HttpErrorResponse && x.status === 401) {
          this.authService.logout();
        }
        return throwError(x);
      })
    );
  }
}