Angular HttpClientとインターセプター 2026年版: リクエスト処理と面接対策
Angular HttpClientの関数型インターセプターを完全解説。認証、キャッシュ、エラーハンドリングのパターンと面接でよく問われる質問をコード例とともに紹介します。

Angular HttpClientインターセプターは、HTTPリクエストの処理方法を根本から変革します。認証ヘッダーの追加からリトライロジックの実装まで、様々な用途に活用できます。Angular 20では、従来のクラスベースのパターンに代わり、より予測可能でツリーシェイキングに適した関数型インターセプターが標準的なアプローチとして推奨されています。
Angular 20では、クラスベースのインターセプターよりもwithInterceptors()を使用した関数型インターセプターが推奨されています。関数型インターセプターは実行順序がより予測可能で、スタンドアロンコンポーネントとの統合も優れています。
provideHttpClientによるHttpClientのセットアップ
インターセプターを記述する前に、アプリケーションのブートストラップでHttpClientを設定する必要があります。Angularのスタンドアロンアーキテクチャでは、従来のHttpClientModuleの代わりにprovideHttpClient()を使用します。
セットアップはアプリケーション設定で行います:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
// Interceptors execute in array order
withInterceptors([authInterceptor, loggingInterceptor])
),
],
});インターセプターは配列で指定された順序で実行されます。最初のインターセプターが送信リクエストを最初に処理し、最後のインターセプターがレスポンスを最初に受け取ります。
関数型インターセプターの構造
関数型インターセプターは、リクエストとnext関数を受け取ります。next(req)を呼び出すと、リクエストがチェーン内の次のインターセプターに渡されるか、これが最後のインターセプターであればバックエンドに送信されます。
基本的な構造は以下のようになります:
import { HttpInterceptorFn, HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http';
import { Observable, tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const startTime = performance.now();
console.log(`[HTTP] ${req.method} ${req.url}`);
return next(req).pipe(
tap({
next: () => {
const duration = Math.round(performance.now() - startTime);
console.log(`[HTTP] ${req.method} ${req.url} completed in ${duration}ms`);
},
error: (err) => {
console.error(`[HTTP] ${req.method} ${req.url} failed:`, err.message);
},
})
);
};このインターセプターはリクエストメソッドとURLをログに記録し、レスポンス時間を計測します。tapオペレーターはレスポンスを変更せずに観察します。
トークン注入を伴う認証インターセプター
ほとんどのアプリケーションでは、送信リクエストに認証トークンを付加する必要があります。HttpRequestオブジェクトはイミュータブルであるため、インターセプターはAuthorizationヘッダーを含むリクエストのクローンを作成します。
このパターンはサービスからトークンを取得し、付加します:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from '../services/auth.service';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
// Inject services using Angular's inject() function
const authService = inject(AuthService);
const token = authService.getAccessToken();
// Skip auth header for public endpoints
if (req.url.includes('/public/') || !token) {
return next(req);
}
// Clone request with Authorization header
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return next(authReq);
};inject()関数は、インターセプターがAngularのインジェクションコンテキスト内で実行されるため動作します。これにより、クラスベースのインターセプターのコンストラクタインジェクションの定型コードが不要になります。
エラーハンドリングとリトライロジック
ネットワーク障害は発生するものです。リトライインターセプターは、指数バックオフを使用して失敗したリクエストを自動的にリトライし、すべてのHTTP呼び出しを変更することなく信頼性を向上させます。
RxJSオペレーターがリトライロジックを処理します:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { retry, timer } from 'rxjs';
export const retryInterceptor: HttpInterceptorFn = (req, next) => {
// Only retry GET requests (idempotent)
if (req.method !== 'GET') {
return next(req);
}
return next(req).pipe(
retry({
count: 3,
delay: (error: HttpErrorResponse, retryCount: number) => {
// Don't retry client errors (4xx)
if (error.status >= 400 && error.status < 500) {
throw error;
}
// Exponential backoff: 1s, 2s, 4s
const delayMs = Math.pow(2, retryCount - 1) * 1000;
console.log(`Retry #${retryCount} in ${delayMs}ms`);
return timer(delayMs);
},
})
);
};POST、PUT、DELETEは冪等ではないため、このインターセプターはGETリクエストのみをリトライします。クライアントエラー(4xx)はサーバーがリクエストを明示的に拒否したため、リトライをスキップします。
Angularの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
HttpContextを使用したレスポンスキャッシング
HttpClientは、インターセプターと呼び出しコードの間でメタデータを受け渡すためのHttpContextを提供します。キャッシングインターセプターは、コンテキストトークンを読み取ってキャッシュの動作を決定できます。
キャッシュトークンとインターセプターは連携して動作します:
import { HttpInterceptorFn, HttpContextToken, HttpResponse } from '@angular/common/http';
import { of, tap } from 'rxjs';
// Context token to control caching per-request
export const CACHE_REQUEST = new HttpContextToken<boolean>(() => false);
const cache = new Map<string, HttpResponse<unknown>>();
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
// Only cache if explicitly requested via context
if (!req.context.get(CACHE_REQUEST) || req.method !== 'GET') {
return next(req);
}
const cacheKey = req.urlWithParams;
const cachedResponse = cache.get(cacheKey);
if (cachedResponse) {
console.log(`[Cache] HIT: ${cacheKey}`);
return of(cachedResponse.clone());
}
return next(req).pipe(
tap((event) => {
if (event instanceof HttpResponse) {
console.log(`[Cache] STORE: ${cacheKey}`);
cache.set(cacheKey, event.clone());
}
})
);
};呼び出しコードはコンテキストトークンでキャッシングをオプトインします:
import { HttpClient, HttpContext } from '@angular/common/http';
import { CACHE_REQUEST } from '../interceptors/cache.interceptor';
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: string) {
return this.http.get(`/api/users/${id}`, {
context: new HttpContext().set(CACHE_REQUEST, true),
});
}
}このパターンにより、キャッシング動作はグローバル設定に隠れるのではなく、呼び出し元で明示的になります。
401処理を伴うトークンリフレッシュ
アクセストークンが期限切れになると、サーバーは401レスポンスを返します。インターセプターはこれをキャッチし、トークンをリフレッシュし、元のリクエストを透過的にリトライできます。
リフレッシュロジックは複数の同時リクエストを調整します:
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, switchMap, throwError, BehaviorSubject, filter, take } from 'rxjs';
import { AuthService } from '../services/auth.service';
let isRefreshing = false;
const refreshTokenSubject = new BehaviorSubject<string | null>(null);
export const tokenRefreshInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status !== 401 || req.url.includes('/auth/refresh')) {
return throwError(() => error);
}
if (isRefreshing) {
// Wait for the refresh to complete
return refreshTokenSubject.pipe(
filter((token) => token !== null),
take(1),
switchMap((token) => {
const retryReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
return next(retryReq);
})
);
}
isRefreshing = true;
refreshTokenSubject.next(null);
return authService.refreshToken().pipe(
switchMap((response) => {
isRefreshing = false;
refreshTokenSubject.next(response.accessToken);
const retryReq = req.clone({
setHeaders: { Authorization: `Bearer ${response.accessToken}` },
});
return next(retryReq);
}),
catchError((refreshError) => {
isRefreshing = false;
authService.logout();
return throwError(() => refreshError);
})
);
})
);
};BehaviorSubjectは、複数のリクエストが同時に失敗した場合の複数のリフレッシュ呼び出しを防止します。トークンがリフレッシュされると、キューに入ったすべてのリクエストが新しいトークンでリトライされます。
面接質問: HttpClientとインターセプター
技術面接では、HTTP処理パターンの理解についてよく質問されます。以下の質問はAngularのポジションで頻繁に登場します。
Q: なぜ関数型インターセプターがクラスベースのインターセプターより好まれるのですか?
関数型インターセプターはより予測可能な実行順序を持ちます。Angular公式ドキュメントによると、withInterceptors()を使用した関数型インターセプターは厳密な配列順序を維持しますが、DIベースのインターセプターは複雑なモジュール階層で順序の問題が発生する可能性があります。また、関数型インターセプターはツリーシェイキングが効果的に機能するため、バンドルサイズが小さくなります。
Q: 特定のリクエストでインターセプターを実行しないようにするにはどうすればよいですか?
2つのアプローチがあります。1つ目は、インターセプター内でURLパターンをチェックし、除外するパスに対しては変更なしでnext(req)を呼び出す方法です。2つ目は、呼び出しコードがインターセプターの動作をオプトアウトするために設定するHttpContextトークンを使用する方法です。コンテキストアプローチは、除外の決定を呼び出し元に置きます。
Q: インターセプターでnext()を呼び出すのを忘れるとどうなりますか?
リクエストはサーバーに到達しません。next()によって返されるObservableは、インターセプターチェーンの残りと実際のHTTP呼び出しを表します。これを呼び出さないと、下流のインターセプターとバックエンドはリクエストを見ることができません。これは、キャッシュされたレスポンスを返すキャッシングインターセプターでは意図的な動作です。
Q: トークンリフレッシュ中の同時リクエストをどのように処理しますか?
BehaviorSubjectを使用して調整します。最初の401がリフレッシュをトリガーしたら、フラグを設定し、サブジェクトにnullを発行します。その後の401は、新しいトークンが到着するまでfilterを使用してサブジェクトを待ちます。これにより、複数のリフレッシュ呼び出しを防止し、保留中のすべてのリクエストが同じ新しいトークンでリトライされることを保証します。
Q: インターセプターはレスポンスボディを変更できますか?
はい。next()によって返されるObservableでmap()を使用して、HttpResponseオブジェクトを変換します。レスポンスはイミュータブルであるため、変更されたボディでクローンします。このパターンは、計算フィールドの追加、APIエンベロープの展開、データ構造の正規化に使用できます。
Angular面接対策の詳細については、HttpClient面接質問モジュールを参照してください。
関数型インターセプターのテスト
インターセプターは、モックされたHTTPハンドラーでテストする必要があります。AngularのHttpTestingControllerは、provideHttpClient()を通じて登録されたインターセプターと連携します。
テストセットアップは以下のようになります:
import { TestBed } from '@angular/core/testing';
import { HttpClient, provideHttpClient, withInterceptors } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { authInterceptor } from './auth.interceptor';
import { AuthService } from '../services/auth.service';
describe('authInterceptor', () => {
let httpClient: HttpClient;
let httpTesting: HttpTestingController;
let authServiceSpy: jasmine.SpyObj<AuthService>;
beforeEach(() => {
authServiceSpy = jasmine.createSpyObj('AuthService', ['getAccessToken']);
TestBed.configureTestingModule({
providers: [
provideHttpClient(withInterceptors([authInterceptor])),
provideHttpClientTesting(),
{ provide: AuthService, useValue: authServiceSpy },
],
});
httpClient = TestBed.inject(HttpClient);
httpTesting = TestBed.inject(HttpTestingController);
});
it('should add Authorization header when token exists', () => {
authServiceSpy.getAccessToken.and.returnValue('test-token');
httpClient.get('/api/data').subscribe();
const req = httpTesting.expectOne('/api/data');
expect(req.request.headers.get('Authorization')).toBe('Bearer test-token');
req.flush({ data: 'test' });
});
it('should skip Authorization for public endpoints', () => {
authServiceSpy.getAccessToken.and.returnValue('test-token');
httpClient.get('/public/health').subscribe();
const req = httpTesting.expectOne('/public/health');
expect(req.request.headers.has('Authorization')).toBeFalse();
req.flush({ status: 'ok' });
});
});このテストは、実際のネットワークリクエストを行わずに、ヘッダーの注入とパブリックエンドポイントの例外の両方を検証します。
本番パターン: インターセプターの組み合わせ
実際のアプリケーションでは、複数のインターセプターをスタックします。順序は重要です:認証はロギングの前に実行され、キャッシングはリトライロジックの前に実行されるべきです。
本番環境の設定は以下のようになります:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { AppComponent } from './app/app.component';
import { authInterceptor } from './interceptors/auth.interceptor';
import { tokenRefreshInterceptor } from './interceptors/token-refresh.interceptor';
import { cacheInterceptor } from './interceptors/cache.interceptor';
import { retryInterceptor } from './interceptors/retry.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([
// Order: auth -> refresh -> cache -> retry -> logging
authInterceptor,
tokenRefreshInterceptor,
cacheInterceptor,
retryInterceptor,
loggingInterceptor,
])
),
],
});この順序により、トークンはリフレッシュ処理の前に付加され、キャッシュチェックはリトライの前に行われ、ロギングは最終的なタイミングをキャプチャします。
広範なAngularパターンについては、インターセプターで使用されるオペレーターを網羅したRxJS基礎モジュールを参照してください。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
Angular HttpClientインターセプターの重要ポイント
withInterceptors()を使用した関数型インターセプターはAngular 20の標準であり、クラスベースの代替よりも予測可能な順序付けとより良いツリーシェイキングを提供します- コンストラクタの定型コードなしでサービスにアクセスするには、インターセプター内で
inject()を使用します HttpRequestオブジェクトはイミュータブルであるため、ヘッダーを変更するにはreq.clone()でリクエストをクローンしますHttpContextトークンにより、リクエストごとの動作はグローバル設定に隠れるのではなく、呼び出し元で明示的になります- トークンリフレッシュインターセプターは、複数のリフレッシュ呼び出しを防ぐために
BehaviorSubjectで同時リクエストを調整する必要があります - インターセプターの順序は重要です:キャッシングの前に認証、リトライの前にキャッシング、最後にロギング
Angular のバグを見つけられますか
実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

執筆
Anthony Fillion-MailletSharpSkill 創業者
10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。
2026年8月22日 更新
共有
関連記事

Angular SignalsとComputedで実現するきめ細かなリアクティビティ:2026年の技術面接対策
Angular 20以降で標準となったSignalsとComputed、linkedSignalの仕組みを解説。技術面接で問われるリアクティビティの設計パターンとRxJSとの使い分けを詳しく説明します。

2026年版 Angular依存性注入の完全ガイド: プロバイダー、トークン、面接対策
Angularの高度な依存性注入(DI)システムを徹底解説。InjectionToken、階層型インジェクター、プロバイダー戦略、そして技術面接で頻出する質問と回答例を網羅的に紹介します。

2026年のAngular制御フロー構文:@if、@for、@switch完全ガイドと面接対策
Angular 17で導入された新しい制御フロー構文(@if、@for、@switch)の詳細解説。従来の構造ディレクティブからの移行方法、パフォーマンス最適化、技術面接で頻出する質問と回答を網羅。