使用 HttpContext 傳遞資料給 HttpInterceptor

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

簡單的 HttpInterceptor 及問題

首先我們先簡單寫一個 AuthInterceptor,當 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 及資料即可,不管是 getpost 還是其他方法,都可以在 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 處理了
    // 所以可能就需要自己處理錯誤
    return of(error);
  })
);

如此一來這次 API 呼叫時,就會將 SUPRESS_401_MESSAGESUPRESS_401_REDIRECT 的值設定為 true,而自訂的 HttpInterceptor 就可以根據這些 token 來決定該如何進一步處理了。

本日小結

要控制 HttpInterceptor,以前常見的做法是主動在 header 加料,然後在 HttpInterceptor 內額外處理,一方面沒有把行為分開,另一方面也容易不小心將不必要的 header 也一起傳出去了;Angular 12 推出 HttpContext 功能後,要在 HttpClient 呼叫時控制 HttpInterceptor 就變得更加容易了!

相關資源

如果您覺得我的文章有幫助,歡迎免費成為 LikeCoin 會員,幫我的文章拍手 5 次表示支持!