Dev Study
← 解説「インターセプター — すべての通信に共通処理を差し込む」に戻る

サンプルコードで身につける: インターセプター — すべての通信に共通処理を差し込む

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1最小のインターセプター — リクエストをログに残す

HttpInterceptorFn型の関数として書く、何もせず通すだけに近い最小のインターセプターです。(req, next)を受け取り、ログを出してからnext(req)で次へ渡します。すべてのリクエストがこの関数を通るため、通信の観測点として最初に作ると動きを理解しやすい例です。

import { HttpInterceptorFn } from '@angular/common/http';

// すべてのリクエストがこの関数を通ってから送信される
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  console.log('送信:', req.method, req.url);
  // next に渡すと次のインターセプター(最後はサーバー)へ進む
  return next(req);
};

2withInterceptorsで登録して有効にする

作ったインターセプターは、provideHttpClient(withInterceptors([...]))に渡して初めて有効になります。登録はアプリ起動時の1か所だけで、各コンポーネントの通信コードには一切手を入れません。「作ったのに動かない」ときは、まずこの登録を忘れていないか確認しましょう。

import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { loggingInterceptor } from './app/logging.interceptor';

// 登録はアプリ起動時のこの1か所だけ
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([loggingInterceptor])),
  ],
});

3認証トークンを全リクエストに付与する

保存済みのトークンをAuthorizationヘッダーとして全リクエストに付ける、インターセプターの代表例です。リクエストは読み取り専用なので、req.clone({ setHeaders: {...} })で複製を作って渡します。ログイン処理を実装したら、必ずセットで用意することになるコードです。

import { HttpInterceptorFn } from '@angular/common/http';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('token');

  // 未ログインならそのまま通す
  if (!token) {
    return next(req);
  }

  // リクエストは読み取り専用。clone で複製してヘッダーを足す
  const authed = req.clone({
    setHeaders: { Authorization: 'Bearer ' + token },
  });
  return next(authed);
};

4インターセプターの中でDIする — サービスからトークンを取る

関数型インターセプターの中でもinject()が使え、認証情報を管理するサービスから値を取り出せます。localStorageを直接触るよりテストしやすく、トークンの持ち方を変えてもインターセプターは修正不要になります。関数の実行がDIコンテキストの中で行われるため、この書き方が成立します。

import { Injectable } from '@angular/core';
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private token = '';

  setToken(token: string) {
    this.token = token;
  }

  getToken() {
    return this.token;
  }
}

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  // 関数型インターセプターの中でも inject() が使える
  const auth = inject(AuthService);
  const token = auth.getToken();

  if (!token) {
    return next(req);
  }
  return next(
    req.clone({ setHeaders: { Authorization: 'Bearer ' + token } })
  );
};

5複数のインターセプターと401の一括処理

ログ用と認証エラー処理用の2つを登録し、withInterceptorsに並べた順にリクエストが通ることを示す例です。401(未認証)をインターセプターで一括して捕まえてログイン画面へ誘導すれば、各画面で同じ処理を繰り返さずに済みます。レスポンス側の共通処理もインターセプターの守備範囲です。

import { bootstrapApplication } from '@angular/platform-browser';
import {
  HttpErrorResponse,
  HttpInterceptorFn,
  provideHttpClient,
  withInterceptors,
} from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { AppComponent } from './app/app.component';

const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  console.log('送信:', req.method, req.url);
  return next(req);
};

// 401(未認証)を全画面ぶんまとめて処理する
const unauthorizedInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  return next(req).pipe(
    catchError((err: HttpErrorResponse) => {
      if (err.status === 401) {
        router.navigate(['/login']); // ログイン画面へ誘導
      }
      return throwError(() => err); // エラー自体は呼び出し元へ流す
    })
  );
};

bootstrapApplication(AppComponent, {
  providers: [
    // 並べた順にリクエストが通る: ログ → 401処理
    provideHttpClient(
      withInterceptors([loggingInterceptor, unauthorizedInterceptor])
    ),
  ],
});