Angular HttpClientとインターセプター 2026年版: リクエスト処理と面接対策

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

Angular HttpClientとインターセプター 2026年版: リクエスト処理と面接対策

Angular HttpClientインターセプターは、HTTPリクエストの処理方法を根本から変革します。認証ヘッダーの追加からリトライロジックの実装まで、様々な用途に活用できます。Angular 20では、従来のクラスベースのパターンに代わり、より予測可能でツリーシェイキングに適した関数型インターセプターが標準的なアプローチとして推奨されています。

関数型インターセプターが標準に

Angular 20では、クラスベースのインターセプターよりもwithInterceptors()を使用した関数型インターセプターが推奨されています。関数型インターセプターは実行順序がより予測可能で、スタンドアロンコンポーネントとの統合も優れています。

provideHttpClientによるHttpClientのセットアップ

インターセプターを記述する前に、アプリケーションのブートストラップでHttpClientを設定する必要があります。Angularのスタンドアロンアーキテクチャでは、従来のHttpClientModuleの代わりにprovideHttpClient()を使用します。

セットアップはアプリケーション設定で行います:

main.tstypescript
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)を呼び出すと、リクエストがチェーン内の次のインターセプターに渡されるか、これが最後のインターセプターであればバックエンドに送信されます。

基本的な構造は以下のようになります:

interceptors/logging.interceptor.tstypescript
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ヘッダーを含むリクエストのクローンを作成します。

このパターンはサービスからトークンを取得し、付加します:

interceptors/auth.interceptor.tstypescript
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オペレーターがリトライロジックを処理します:

interceptors/retry.interceptor.tstypescript
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を提供します。キャッシングインターセプターは、コンテキストトークンを読み取ってキャッシュの動作を決定できます。

キャッシュトークンとインターセプターは連携して動作します:

interceptors/cache.interceptor.tstypescript
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());
      }
    })
  );
};

呼び出しコードはコンテキストトークンでキャッシングをオプトインします:

services/user.service.tstypescript
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レスポンスを返します。インターセプターはこれをキャッチし、トークンをリフレッシュし、元のリクエストを透過的にリトライできます。

リフレッシュロジックは複数の同時リクエストを調整します:

interceptors/token-refresh.interceptor.tstypescript
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()を通じて登録されたインターセプターと連携します。

テストセットアップは以下のようになります:

interceptors/auth.interceptor.spec.tstypescript
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' });
  });
});

このテストは、実際のネットワークリクエストを行わずに、ヘッダーの注入とパブリックエンドポイントの例外の両方を検証します。

本番パターン: インターセプターの組み合わせ

実際のアプリケーションでは、複数のインターセプターをスタックします。順序は重要です:認証はロギングの前に実行され、キャッシングはリトライロジックの前に実行されるべきです。

本番環境の設定は以下のようになります:

main.tstypescript
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-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月22日 更新

共有

関連記事