使用 HttpContext 傳遞資料給 HttpInterceptor

Angular 中的 HttpInterceptor 可以幫助我們攔截每個 HttpClient 送出的呼叫,幫助我們在呼叫前後打點各種大小事情,不過有時候我們反而希望 HttpInterctptor 不要自作主張幫我們處理太多事情,之前有些過一篇文章介紹 如何忽略 HTTP_INTERCEPTORS ,而到了 Angular 12 之後,則內建了 HttpContext 的功能,方便在程式中主動傳遞一些資料給我們自己設計的 HttpInterctptor,來達到一些更細緻的操作,這篇文章就來看一下 HttpContext 該如何使用。

簡單的 HttpInterceptor 及問題

首先我們先簡單寫一個 AuthInterctptor ,當 HttpClient 呼叫得到 401 錯誤時,提示訊息並將畫面轉到 /login

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse,
} from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private router: Router) {}
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(newRequest).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          alert('登入逾時或權限不足,請重新登入');
          this.router.navigateByUrl('/login');
        return throwError(error)

雖然很簡單,但有些時候我們希望不要提是錯誤並轉頁,例如我們本來就在 /login 頁面時,如果輸入帳號密碼錯誤回傳 401 時應該要自己定義其他的提示,希望 HttpInterceptor 可以根據我們要的情況有不同的處理行為。

這時候我們就可以在呼叫時傳遞一個 HttpContextToken ,而在我們自己撰寫的 AuthInterceptor 內,則可以判斷指定 HttpContext 的內容,決定後續要如何處理。

使用 HttpContextToken

定義 HttpContextToken

首先我們要先定義好需要的 HttpContextToken

import { HttpContextToken } from '@angular/common/http';
 * 當 401 錯誤時是否提示訊息
export const SUPRESS_401_MESSAGE = new HttpContextToken<boolean>(() => false);
 * 當 401 錯誤時是否轉到登入頁面
export const SUPRESS_401_REDIRECT = new HttpContextToken<boolean>(() => false);

每個 HttpContextToken 建立時候需要傳入一個 callback function,這個 callback function 會回傳 HttpContextToken 的預設值,也就是當我們使用 HttpClient 呼叫時,若沒有指定 HttpContextToken 內容,則會以此為預設值。

在 HttpInterceptor 內取得 HttpContextToken

在自訂的 HttpInterceptor 內,可以使用 HttpRequest.context.get 得到原來 HttpClient 呼叫時傳入的 HttpContextToken ,如同前面所說,如果 HttpClient 呼叫時沒指定,則會使用最早建立 token 時指定的預設值。

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
  return next.handle(newRequest).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        // 使用 request.context.get 取得執定的 HttpContextToken
        // 並根據 token 內容決定如何進一步執行程式
        if(!request.context.get(SUPRESS_401_MESSAGE)) {
          alert('登入逾時或權限不足,請重新登入');          
        if(!request.context.get(SUPRESS_401_REDIRECT)) {
          this.router.navigateByUrl('/login');          
      return throwError(error)

HttpClient 傳遞 HttpContextToken

之後在 HttpClient 呼叫時,如果需要傳遞 HttpContextToken 讓 HttpInterceptor 根據條件處理不同行為時,只要在呼叫時設定好要使用哪些 HttpContextToken 及資料即可,不管是 get post 還是其他方法,都可以在 options 參數內傳遞 context 參數:

this.httpClient.post<any>(
  `api/ligin`, 
  loginData, 
    context: new HttpContext()
      .set(SUPRESS_401_MESSAGE, true)
      .set(SUPRESS_401_REDIRECT, true)
).pipe(
  catchError(error => {
    // 因為不讓 HttpInterceptor 處理了