HttpClient and APIs
Calling a backend API from an Angular app with HttpClient and handling the observable it returns.
読了時間 2 分
Nearly every real application needs to talk to a server. Angular ships HttpClient for exactly this, built on the same observable pattern the router uses.
Registering HttpClient
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()],
};Like the router, HttpClient needs to be registered once with provideHttpClient() in the app's configuration before it can be injected anywhere.
Making a request from a service
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Article {
id: number;
title: string;
body: string;
}
@Injectable({ providedIn: 'root' })
export class ArticleService {
private http = inject(HttpClient);
private baseUrl = '/api/articles';
getAll(): Observable<Article[]> {
return this.http.get<Article[]>(this.baseUrl);
}
getById(id: number): Observable<Article> {
return this.http.get<Article>(`${this.baseUrl}/${id}`);
}
create(article: Omit<Article, 'id'>): Observable<Article> {
return this.http.post<Article>(this.baseUrl, article);
}
}HTTP calls belong in a service, not directly in a component — it keeps the fetching logic reusable and testable independent of any one screen that happens to display the data. Every HttpClient method (get, post, put, delete) returns an observable, and — crucially — that observable does nothing until something subscribes to it. Calling this.http.get(...) alone never sends a request.
Consuming it in a component
import { Component, inject } from '@angular/core';
import { ArticleService, Article } from './article.service';
@Component({
selector: 'app-article-list',
template: `
@for (article of articles$ | async; track article.id) {
<h2>{{ article.title }}</h2>
} @empty {
<p>Loading articles...</p>
}
`,
})
export class ArticleListComponent {
private articleService = inject(ArticleService);
articles$ = this.articleService.getAll();
}The async pipe subscribes to articles$ when the template renders, which is what actually triggers the HTTP request — and unsubscribes automatically when the component is destroyed, which for a single HTTP call mostly matters if the component is destroyed before the response arrives, avoiding a stray update to a component that no longer exists.
Handling errors
import { catchError, of } from 'rxjs';
getAll(): Observable<Article[]> {
return this.http.get<Article[]>(this.baseUrl).pipe(
catchError((error) => {
console.error('Failed to load articles', error);
return of([]);
}),
);
}catchError intercepts an error from the HTTP call and lets you recover — here, logging it and falling back to an empty array rather than letting the error propagate and break the subscriber. Without a catchError, a failed request simply never emits a value on the success path, and any UI depending on it (like the @empty block above) would be stuck showing its loading state indefinitely.
Typing the response
Passing a type argument, as in http.get<Article[]>(...), doesn't validate the response at runtime — it only tells TypeScript what shape to expect, purely for compile-time checking. If the backend's actual response doesn't match, Angular won't catch that mismatch for you; validating untrusted response data is a separate concern from typing it.