Angular基礎 #7 HttpClient で API 呼び出し
前回は Router で複数ページを構成する方法を学びました。これで最後のパズルピースが残っています。画面を出してルーティングまでしたら、結局は バックエンドサーバーからデータを受け取ってこそ 本当のアプリになります。今回は Angular の標準通信ツール HttpClient を扱っていきます。
HttpClient セットアップ #
Angular は fetch や axios の代わりに自前の HTTP クライアントである HttpClient を推奨します。インターセプタ、テストユーティリティ、RxJS 統合が一式入っており、Angular の SSR (サーバーサイドレンダリング) でも自然に動作します。
まずアプリ設定に 1 行追加する必要があります。Standalone パターンでは app.config.ts に provideHttpClient() を登録します。
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
],
};この 1 行でアプリ全体で HttpClient を注入して使えるようになります。
HttpClientModule を import する方式は NgModule 時代のパターンです。新しいプロジェクトでは provideHttpClient() を使ってください。Standalone 時代の標準です。GET リクエスト #
もっとも基本的な GET リクエストから見ていきます。ユーザー一覧を取ってくるサービスを作ってみます。
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private apiUrl = 'https://jsonplaceholder.typicode.com/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
}3 つのポイントがあります。
inject(HttpClient)で HttpClient を注入してもらいます。コンストラクタ注入 (constructor(private http: HttpClient)) も依然として動作しますが、最近の Angular ではinject()の関数型注入が好まれます。get<User[]>(url)のように ジェネリクスでレスポンスの型 を指定します。そうするとgetUsers()の戻り値の型がObservable<User[]>と推論され、型安全に扱えます。- HttpClient のメソッドは すぐにリクエストを送らず
Observableを返します。誰かが購読 (subscribe) して初めてリクエストが出ます。
Observable を扱う #
サービスが返した Observable をコンポーネントで受け取って画面に描画してみましょう。もっとも単純な方法は subscribe で直接結果を受け取ることです。
import { Component, OnInit, inject, signal } from '@angular/core';
import { UserService, User } from './user.service';
@Component({
selector: 'app-user-list',
standalone: true,
template: `
<ul>
@for (user of users(); track user.id) {
<li>{{ user.name }} ({{ user.email }})</li>
}
</ul>
`,
})
export class UserListComponent implements OnInit {
private userService = inject(UserService);
users = signal<User[]>([]);
ngOnInit() {
this.userService.getUsers().subscribe(data => {
this.users.set(data);
});
}
}subscribe のコールバックの中で受け取ったデータをシグナルに入れ、テンプレートは users() を呼び出して自動的に更新されます。
ただし subscribe を直接使うときには メモリリーク に注意する必要があります。コンポーネントが消えても購読が生き残ると、もはや存在しないコンポーネントの状態を更新しようとしてエラーやリークが発生する可能性があります。この問題は次のシリーズ (中級) で本格的に扱いますが、今から推奨できるより良いパターンが 1 つあります。
toSignal で Observable → Signal 変換
#
Angular 16 から入った toSignal は Observable をシグナルに自動変換してくれます。購読解除を気にしなくてもコンポーネントが消えると自動で整理され、テンプレートではただシグナルのように呼び出せば構いません。
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
standalone: true,
template: `
@if (users(); as list) {
<ul>
@for (user of list; track user.id) {
<li>{{ user.name }} ({{ user.email }})</li>
}
</ul>
} @else {
<p>読み込み中...</p>
}
`,
})
export class UserListComponent {
private userService = inject(UserService);
users = toSignal(this.userService.getUsers());
}subscribe がなくなり、OnInit も必要なくなりました。toSignal が内部で自動的に購読・解除まで処理してくれるからです。最初は undefined を返すので、上のように @if (users(); as list) でロード状態を分岐できます。
toSignal を優先的に検討してください。直接 subscribe するパターンよりリークの危険性が少なく、シグナルベースのテンプレートともよく合います。POST/PUT/DELETE リクエスト #
読み取りだけのアプリはほぼありません。データを作って修正して消すメソッドも似た形で書きます。
// 作成
createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}
// 全体の修正
updateUser(user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${user.id}`, user);
}
// 削除
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}post と put の 2 番目の引数が リクエストの body です。JSON のシリアライズは HttpClient が自動でやってくれます。呼び出す側は GET と同じく Observable を受け取って処理します。
this.userService
.createUser({ name: '太郎', email: 'taro@example.com' })
.subscribe(created => console.log('作成されました:', created));エラー処理 #
ネットワークが常にうまく動作すると仮定してはいけません。Angular は RxJS の catchError 演算子でエラーを横取りする方式を推奨します。
import { catchError, EMPTY } from 'rxjs';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl).pipe(
catchError(err => {
console.error('ユーザー一覧の取得に失敗しました:', err);
return EMPTY;
})
);
}pipe は Observable に演算子を並べて適用するための通り道です。catchError はエラーが発生したときに元の流れを差し替える新しい Observable を返さなければなりません。上の例のように EMPTY (何も値を流さずすぐに終了) を返すと、購読者にはただデータが来なかったように見えます。
デフォルト値を流したければ of([]) を使えば構いません。
import { of } from 'rxjs';
return this.http.get<User[]>(this.apiUrl).pipe(
catchError(() => of([])) // エラー時は空配列でフォールバック
);ヘッダーとオプション #
API に認証トークンを送ったり query parameter を付ける必要があるときが多いです。HttpClient の 2 (または 3) 番目の引数にオプションオブジェクトを渡せば構いません。
import { HttpHeaders, HttpParams } from '@angular/common/http';
getPosts(userId: number, token: string): Observable<Post[]> {
const headers = new HttpHeaders({
Authorization: `Bearer ${token}`,
});
const params = new HttpParams().set('userId', userId);
return this.http.get<Post[]>('/api/posts', { headers, params });
}HttpParams を使わず単にオブジェクトとして渡す短い形式も可能です。
this.http.get<Post[]>('/api/posts', {
params: { userId, limit: 10 },
});Interceptor 一行紹介 #
リクエストごとにトークンを直接付けるのは面倒です。Angular はすべての HTTP リクエストを横取りして共通処理を差し込む Interceptor を提供します。
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('token');
if (!token) return next(req);
const cloned = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
return next(cloned);
};app.config.ts に登録すれば終わりです。
provideHttpClient(withInterceptors([authInterceptor])),リクエストのロギング、トークンの更新、共通エラーハンドリング、キャッシュ — これらすべてがインターセプタ 1 箇所で整理されます。詳しいパターンは中級シリーズで扱う予定なので、今は「こういうものがある」程度に覚えておいてください。
まとめ #
今回の記事では HttpClient でバックエンドと通信する方法を整理しました。
provideHttpClient()でセットアップしてinject(HttpClient)で注入してもらうget<T>/post<T>/put<T>/delete<T>— ジェネリクスで型安全に- 結果は Observable。直接
subscribeするかtoSignalでシグナルに変換して扱う catchError+EMPTY/ofでエラーフォールバックHttpHeaders、HttpParamsあるいはオブジェクトでヘッダーと query params- Interceptor で共通処理 (認証・ロギング・エラー) を 1 箇所にまとめる
ここまでが Angular基礎 の最後の記事です。1 編で Angular とは何かから始まり、コンポーネントとテンプレート構文、データバインディング、Directive と Pipe、Service と依存性注入、Router、そして今日の HttpClient まで — 小さな Angular アプリを最初から最後まで作るのに必要な核心ツールはすべて 1 度ずつ扱いました。これで皆さんは Angular で「データを受け取って画面に描画して、ユーザーの入力を処理して、複数のページを行き来する」基本を備えたことになります。
次のステップは 「Angular 中級講座」 です。基礎では意図的に後回しにしたテーマを本格的に扱う予定です。
- Reactive Forms — 大きなフォーム・検証・動的フォームをきれいに扱うモデルベースのフォーム
- RxJS 深化 —
switchMap、debounceTime、combineLatestなどよく使う演算子と検索・自動補完・リトライのような実戦パターン - Lifecycle と Change Detection —
OnInitを超えるライフサイクル、OnPush 戦略、シグナルと Zone.js の関係 - HTTP Interceptor パターン — トークンの更新、キャッシュ、共通エラーハンドリング
- Standalone Routing 深化 — Lazy loading、Route Guards、Resolver、ルートデータ
基礎が「ツールの使い方」だったとすれば、中級は「このツールで実際の製品をどう作るか」の領域です。基礎講座を最後まで一緒についてきてくださった皆さん、本当にお疲れさまでした。中級講座でより深い話で再びお会いしましょう。