サンプルコードで身につける: インターセプター — すべての通信に共通処理を差し込む
解説で学んだ概念を、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])
),
],
});