angular httpclient

Angular HttpClient: すべてのリクエストを征服するための究極のガイド

この包括的なガイドでは、HttpClient を使用して Angular アプリケーションで HTTP リクエストを行う方法を説明します。基本的なセットアップから高度な使用方法、テスト手法まで、HttpClient をマスターするために知っておく必要があるすべての情報を提供します。

はじめに:HttpClient とは?

フロントエンドアプリケーションは、通常、データの取得、フォームの送信、ファイルのアップロードなど、バックエンドサービスと通信するために HTTP プロトコルを使用します。ブラウザは、HTTP リクエストを行うための複数の API を提供しており、例えば、従来の XMLHttpRequest や最新の fetch() API などがあります。Angular の HttpClient モジュールは、XMLHttpRequest を基盤として構築されており、以下のような多くの拡張機能を提供しています。

  • テスト容易性: ユニットテストと統合テストを容易に実行できます。

  • 型付け: TypeScript の型チェックをサポートしており、コードの安全と可読性を向上させます。

  • インターセプト: リクエストとレスポンスをインターセプトできるため、認証、キャッシュ、ログ記録などのグローバルな処理ロジックを実装できます。

  • Observable API: RxJS Observables を使用して非同期リクエストを処理し、強力な演算子とチェーン呼び出し機能を提供します。

  • 簡略化されたエラー処理: 一貫したエラー処理メカニズムを提供し、エラーの捕捉と処理を容易にします。

HttpClient の設定

HttpClient を使用する前に、まず HttpClientModule をルートモジュール AppModule にインポートする必要があります。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  // ... その他のモジュール設定 ...
})
export class AppModule { }

HttpClientModule をインポートしたら、サービスやコンポーネントで HttpClient インスタンスを注入できるようになります。例えば、ConfigService に HttpClient を注入します。

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

@Injectable({ providedIn: 'root' })
export class ConfigService {
  constructor(private http: HttpClient) { }
}

サーバーへのデータリクエスト

HttpClient は、さまざまな種類の HTTP リクエストを行うための複数のメソッドを提供します。最もよく使用されるのは、データを取得するための get() メソッドです。以下の例では、get() メソッドを使用して、JSON 形式の設定ファイル config.json を取得する方法を示します。

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

interface Config {
  apiUrl: string;
  apiKey: string;
}

@Injectable({ providedIn: 'root' })
export class ConfigService {
  private configUrl = 'assets/config.json';

  constructor(private http: HttpClient) { }

  getConfig() {
    return this.http.get<Config>(this.configUrl);
  }
}

上記の例では、まず設定ファイルの構造を記述する Config インターフェースを定義しています。次に、getConfig() メソッドで、http.get<Config>(this.configUrl) を使用して GET リクエストを行い、レスポンスの型を Config に指定しています。

サービスでデータアクセスをカプセル化する利点:

データアクセスロジックをサービスにカプセル化することで、データの取得とデータの処理を分離し、コードの保守性と再利用性を向上させることができます。また、サービスは複数のコンポーネントで共有できるため、コードの重複を避けることができます。

型付けされたレスポンスのリクエスト

TypeScript の型システムは、エラーを早期に発見し、コードの可読性を向上させるのに役立ちます。HttpClient は、レスポンスオブジェクトの型を指定することで、TypeScript の型チェック機能を活用することができます。

上記の例では、すでに get<Config>(this.configUrl) を使用して、レスポンスの型を Config インターフェースに指定しています。これにより、TypeScript コンパイラーは、レスポンスデータが Config インターフェースの構造に準拠しているかどうかをチェックします。

完全なレスポンスの読み取り

場合によっては、レスポンス本文のデータだけでなく、ステータスコードやコンテンツタイプなどのレスポンスヘッダー情報にもアクセスする必要があります。HttpClient は、監視するレスポンス部分を指定するための observe オプションを提供しています。

以下の例では、完全なレスポンスを読み取る方法を示します。

getConfigResponse(): Observable<HttpResponse<Config>> {
  return this.http.get<Config>(this.configUrl, { observe: 'response' });
}

observe を 'response' に設定することで、HttpClient.get() メソッドは、ヘッダーと本文を含む完全なレスポンス情報を持つ HttpResponse オブジェクトを返します。

JSONP リクエストの実行

JSONP は、ブラウザの同一生成元ポリシーの制限を回避するために使用されるクロスドメインリクエスト技術です。ターゲットサーバーが CORS プロトコルをサポートしていない場合、JSONP を使用してクロスドメインリクエストを実行できます。

Angular は、JSONP リクエストをサポートする HttpClientJsonpModule モジュールを提供しています。まず、HttpClientJsonpModule をアプリケーションモジュールにインポートする必要があります。

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

@NgModule({
  imports: [
    // ... その他のモジュール ...
    HttpClientJsonpModule
  ],
  // ... その他のモジュール設定 ...
})
export class AppModule { }

次に、HttpClient の jsonp() メソッドを使用して JSONP リクエストを実行できます。

searchHeroes(term: string): Observable<Hero[]> {
  const url = `${this.heroesUrl}?search=${term}&callback=JSONP_CALLBACK`;
  return this.http.jsonp<Hero[]>(url, 'JSONP_CALLBACK');
}

上記の例では、コールバック関数名 JSONP_CALLBACK を jsonp() メソッドにパラメータとして渡しています。サーバーは、ブラウザが正しく解析できるように、このコールバック関数名でレスポンスデータをラップします。

JSON 以外のデータのリクエスト

すべての API が JSON データを返すわけではありません。例えば、プレーンテキスト、HTML、またはバイナリデータを返す API もあります。HttpClient の responseType オプションを使用すると、期待されるレスポンスの型を指定できます。

以下の例では、プレーンテキストファイルを取得する方法を示します。

getTextFile(filename: string): Observable<string> {
  return this.http.get(filename, { responseType: 'text' });
}

responseType を 'text' に設定することで、HttpClient.get() メソッドは、テキストファイルの内容を含む文字列型の Observable を返します。

tap 演算子を使用したログ記録とエラー処理:

tap 演算子は、データストリームを変更せずに副作用操作 (ログ記録、デバッグなど) を実行するために使用できます。以下の例では、テキストファイルを取得する際に tap 演算子を使用してログを記録する方法を示します。

getTextFile(filename: string): Observable<string> {
  return this.http.get(filename, { responseType: 'text' }).pipe(
    tap(
      data => console.log('ファイルの取得に成功:', data),
      error => console.error('ファイルの取得に失敗:', error)
    )
  );
}

エラー処理

HTTP リクエストは、サーバーエラーやネットワーク接続の問題など、さまざまな理由で失敗する可能性があります。HttpClient の subscribe() メソッドは、エラーが発生した場合に処理するためのエラーコールバック関数を用意しています。

以下の例では、エラーコールバック関数を使用してエラーを処理する方法を示します。

getConfig() {
  this.http.get<Config>(this.configUrl).subscribe(
    config => this.config = config,
    error => console.error('設定の取得に失敗:', error)
  );
}

HttpErrorResponse オブジェクトを使用したエラー詳細の取得:

subscribe() メソッドのエラーコールバック関数は、HttpErrorResponse オブジェクトを受け取ります。このオブジェクトには、ステータスコードやエラーメッセージなど、エラーに関する詳細情報が含まれています。

以下の例では、サービスでエラーを処理する方法を示します。

private handleError(error: HttpErrorResponse) {
  if (error.error instanceof ErrorEvent) {
    // クライアントエラーまたはネットワークエラー
    console.error('クライアントエラー:', error.error.message);
  } else {
    // バックエンドエラー
    console.error(`バックエンドエラー: ${error.status} ${error.statusText}`);
  }

  return throwError('エラーが発生しました。後でもう一度試してください。');
}

再試行

ネットワーク接続が不安定な場合、HTTP リクエストは断続的に失敗する可能性があります。retry 演算子を使用すると、最大再試行回数に達するまで、失敗したリクエストを自動的に再試行できます。

以下の例では、retry 演算子を使用してリクエストを 3 回再試行する方法を示します。

getConfig() {
  return this.http.get<Config>(this.configUrl).pipe(
    retry(3),
    catchError(this.handleError)
  );
}

Observables と演算子

HttpClient のすべてのメソッドは、RxJS Observables を返します。Observables は、非同期操作を処理するための強力なツールであり、データストリームを変換、フィルタリング、結合するための一連の演算子を提供します。

catchError 演算子は、Observable ストリーム内のエラーを捕捉し、エラー処理ロジックを実行するために使用されます。retry 演算子は、失敗した Observable を再試行するために使用されます。

HTTP ヘッダー

HTTP ヘッダーには、コンテンツタイプや認証情報など、リクエストとレスポンスに関するメタデータが含まれています。HttpHeaders クラスは、HTTP ヘッダーを追加および更新するための便利な方法を提供します。

以下の例では、httpOptions オブジェクトを使用して HTTP ヘッダーを設定する方法を示します。

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

const httpOptions = {
  headers: new HttpHeaders({
    'Content-Type':  'application/json',
    'Authorization': 'my-auth-token'
  })
};

その後、HttpClient メソッドで httpOptions オブジェクトを使用できます。

getHeroes(): Observable<Hero[]> {
  return this.http.get<Hero[]>(this.heroesUrl, httpOptions);
}

サーバーへのデータ送信

HttpClient は、サーバーにデータを送信するための put()post()delete() メソッドを提供しています。以下の例では、これらのメソッドを使用して、ヒーローデータを追加、更新、削除する方法を示します。

ヒーローの追加:

addHero(hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions);
}

ヒーローの更新:

updateHero(hero: Hero): Observable<any> {
  const url = `${this.heroesUrl}/${hero.id}`;
  return this.http.put(url, hero, httpOptions);
}

ヒーローの削除:

deleteHero(id: number): Observable<any> {
  const url = `${this.heroesUrl}/${id}`;
  return this.http.delete(url, httpOptions);
}

高度な使用方法:HTTP インターセプター

HTTP インターセプターは、アプリケーションに出入りする HTTP リクエストとレスポンスをインターセプトし、認証ヘッダーの追加、データのキャッシュ、ログの記録などのカスタムロジックを実行できます。

インターセプターの作成:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = this.authService.getToken();

    if (authToken) {
      const authReq = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${authToken}`)
      });

      return next.handle(authReq);
    } else {
      return next.handle(req);
    }
  }
}

インターセプターの提供:

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

インターセプターの動作原理:

インターセプターは、intercept() メソッドを使用してリクエストとレスポンスをインターセプトします。next オブジェクトは、インターセプターチェーン内の次のインターセプターを表します。インターセプターがリクエストを処理しない場合は、next.handle(req) を呼び出して、リクエストを次のインターセプターに渡す必要があります。

インターセプターの順序:

インターセプターは、提供された順序で実行されます。例えば、A、B、C の 3 つのインターセプターが提供された場合、リクエストは A -> B -> C の順序で各インターセプターを通過し、レスポンスは C -> B -> A の順序で返されます。

不変性:

HttpRequest と HttpResponse オブジェクトは不変であるため、インターセプターは直接変更できません。リクエストまたはレスポンスを変更する必要がある場合は、clone() メソッドを使用してコピーを作成し、コピーを変更する必要があります。

インターセプターの使用例:

  • リクエスト本文の変更: 例えば、リクエスト本文にタイムスタンプや署名情報を追加します。

  • デフォルトヘッダーの設定: 例えば、すべてのリクエストに認証ヘッダーを追加します。

  • 認証: 例えば、ユーザーがログインしているかどうかを確認し、認証ヘッダーを追加します。

  • キャッシュ: 例えば、GET リクエストのレスポンスをキャッシュして、パフォーマンスを向上させます。

  • ログ記録: 例えば、すべてのリクエストとレスポンスを記録して、デバッグや監視を行います。

URL クエリ文字列

HttpParams クラスは、URL クエリ文字列を構築し、リクエストに追加するための便利な方法を提供します。

以下の例では、HttpParams を使用してクエリパラメータを追加する方法を示します。

searchHeroes(term: string): Observable<Hero[]> {
  const params = new HttpParams().set('name', term);
  return this.http.get<Hero[]>(this.heroesUrl, { params });
}

リクエストのデバウンス

ユーザーが検索ボックスにすばやく入力すると、頻繁なリクエストがトリガーされ、サーバーの負荷が高くなる可能性があります。RxJS の debounceTimedistinctUntilChangedswitchMap 演算子を使用して、ユーザー入力に基づくリクエストを遅延させて最適化することができます。

以下の例では、リクエストのデバウンスを実装する方法を示します。

search(term: string) {
  this.searchTerms.next(term);
}

ngOnInit() {
  this.heroes$ = this.searchTerms.pipe(
    debounceTime(300),        // 300 ミリ秒待つ
    distinctUntilChanged(),   // 重複した検索語句を無視する
    switchMap(term => this.heroService.searchHeroes(term))  // リクエストを実行する
  );
}

進捗イベントのリスニング

ファイルのアップロードなどの長時間実行される操作では、進捗イベントをリスニングして、ユーザーにリアルタイムのフィードバックを提供できます。

以下の例では、アップロードの進捗イベントをリスニングする方法を示します。

upload(file: File): Observable<HttpEvent<any>> {
  const formData = new FormData();
  formData.append('file', file, file.name);

  const req = new HttpRequest('POST', this.uploadUrl, formData, {
    reportProgress: true
  });

  return this.http.request(req);
}

reportProgress: true オプションは、進捗イベントのリスニングを有効にします。HttpEventType 列挙型には、SentUploadProgressResponse などのすべての可能なイベントタイプが含まれています。

セキュリティ: XSRF 対策

クロスサイトリクエストフォージェリ (XSRF) は、攻撃者がユーザーがログインしている Web サイトのアイデンティティを利用して、ユーザーが知らないうちに悪意のある操作を実行する攻撃です。HttpClient は、組み込みの XSRF 対策メカニズムを提供しています。

デフォルトでは、HttpClient は XSRF-TOKEN という名前の Cookie 値を、すべての変更操作 (POST、PUT、DELETE など) のリクエストヘッダーに自動的に追加します。サーバー側はこのヘッダーを検証して、リクエストが正当な送信元からのものであることを確認する必要があります。

カスタム Cookie/ヘッダー名の設定:

バックエンドサービスが異なる Cookie またはヘッダー名を使用している場合は、HttpClientXsrfModule.withOptions() メソッドを使用して設定をカスタマイズできます。

@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'MY_XSRF_TOKEN',
      headerName: 'X-MY-XSRF-TOKEN'
    })
  ],
  // ... その他のモジュール設定 ...
})
export class AppModule { }

HTTP リクエストのテスト

HttpClientTestingModule と HttpTestingController は、HTTP リクエストをモックしてユニットテストと統合テストを実行するためのツールを提供します。

以下の例では、HTTP GET リクエストをテストする方法を示します。

it('should get heroes', () => {
  service.getHeroes().subscribe(heroes => {
    expect(heroes).toEqual(mockHeroes);
  });

  const req = httpTestingController.expectOne(heroesUrl);
  expect(req.request.method).toEqual('GET');
  req.flush(mockHeroes);
});

HttpTestingController は HTTP リクエストをインターセプトし、サーバーレスポンスをシミュレートできます。expectOne() メソッドは、特定の URL へのリクエストが行われたことをアサートするために使用され、flush() メソッドはサーバーレスポンスをシミュレートするために使用されます。

結論

HttpClient は、Angular フレームワークで HTTP リクエストを処理するための強力なツールです。型付け、インターセプト、エラー処理、進捗イベントのリスニングなど、豊富な機能を提供しており、効率的で信頼性の高い Web アプリケーションの構築に役立ちます。このガイドでは、HttpClient のあらゆる側面を詳しく説明し、多くのサンプルコードを提供しました。この強力な機能を最大限に活用するのに役立つことを願っています。

参考資料

よくある質問

Q1:HttpClient を使用して複数の HTTP リクエストを並行して実行するにはどうすればよいですか?

A1: RxJS の forkJoin 演算子を使用して、複数の Observables を結合し、すべてのリクエストが完了したときに結果を取得できます。

Q2:HttpClient リクエストをキャンセルするにはどうすればよいですか?

A2: subscribe() メソッドによって返されるサブスクリプションオブジェクトの unsubscribe() メソッドを呼び出すことができます。

Q3:HttpClient を使用してファイルアップロードの進捗状況を表示するにはどうすればよいですか?

A3: リクエストに reportProgress: true オプションを設定し、HttpEventType.UploadProgress イベントをリスンします。