On this page
HttpClient: consuming REST APIs
HttpClient in Angular
HttpClient is Angular's built-in service for communicating with REST APIs. It returns typed Observables and supports interceptors, error handling, and transformations.
Configuring HttpClient
In app.config.ts, provide the HTTP client:
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withFetch(), // Use native Fetch API
withInterceptors([authInterceptor]),
),
],
};HTTP methods
HttpClient provides methods for each HTTP verb:
const http = inject(HttpClient);
// GET - Retrieve data
http.get<User[]>('/api/users');
// POST - Create resource
http.post<User>('/api/users', { name: 'Ana' });
// PUT - Replace entire resource
http.put<User>('/api/users/1', completeData);
// PATCH - Partially update
http.patch<User>('/api/users/1', { name: 'Ana Maria' });
// DELETE - Remove resource
http.delete<void>('/api/users/1');Query parameters
Use HttpParams to add query parameters:
import { HttpParams } from '@angular/common/http';
const params = new HttpParams()
.set('page', 1)
.set('limit', 20)
.set('sort', 'date');
http.get<Result>('/api/search', { params });
// GET /api/search?page=1&limit=20&sort=dateCustom headers
import { HttpHeaders } from '@angular/common/http';
const headers = new HttpHeaders()
.set('Authorization', `Bearer ${token}`)
.set('Accept-Language', 'en');
http.get('/api/data', { headers });Functional interceptors
Interceptors process ALL HTTP requests and responses:
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService);
const token = auth.token();
if (token) {
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
return next(authReq);
}
return next(req);
};Register interceptors in the configuration:
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor]),
)Error handling
Use catchError from RxJS to handle HTTP errors:
import { catchError } from 'rxjs/operators';
import { of, throwError } from 'rxjs';
getUser(id: number) {
return this.http.get<User>(`/api/users/${id}`).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 404) {
return of(null); // Return null if not found
}
return throwError(() => error); // Re-throw other errors
}),
);
}HttpClient with resource
You can combine HttpClient with rxResource for reactive loading:
import { rxResource } from '@angular/core/rxjs-interop';
readonly userId = signal(1);
readonly user = rxResource({
request: () => ({ id: this.userId() }),
loader: ({ request }) =>
this.http.get<User>(`/api/users/${request.id}`),
});Best practices
- Type the responses — Always use generics:
get<MyType>() - Centralize in services — Don't call HttpClient from components
- Handle errors — Use
catchErrorfor failed responses - Use interceptors — For authentication, logging, and retry
Practice
- Build a CRUD service: Implement a service with
getAll(),getById(),create(), andremove()methods usingHttpClientagainsthttps://jsonplaceholder.typicode.com/posts. Type all responses with generics. - Add a logging interceptor: Create a functional interceptor that logs the URL and method of every HTTP request to the console. Register it with
withInterceptors()in the configuration. - Handle errors with catchError: In the
getById()method, usecatchErrorto returnnullwhen the server responds with 404 and re-throw other errors.
In the next lesson, we will learn the essential RxJS operators for transforming and combining data streams.
provideHttpClient
Register HttpClient in app.config.ts with provideHttpClient(withFetch()). The withFetch() option uses the browser's native Fetch API, which is more efficient than XMLHttpRequest.
Subscriptions
HttpClient returns cold Observables that automatically complete after the response. But in components, remember to clean up long-lived subscriptions with takeUntilDestroyed() or DestroyRef.
import { Injectable, inject, signal, computed } from '@angular/core';
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';
import { of } from 'rxjs';
interface Article {
id: number;
title: string;
content: string;
author: string;
date: string;
}
interface PageResponse {
data: Article[];
total: number;
page: number;
}
@Injectable({ providedIn: 'root' })
export class ArticlesService {
private readonly http = inject(HttpClient);
private readonly baseUrl = '/api/articles';
private readonly _loading = signal(false);
readonly loading = this._loading.asReadonly();
getAll(page = 1, limit = 10) {
this._loading.set(true);
const params = new HttpParams()
.set('page', page)
.set('limit', limit);
return this.http
.get<PageResponse>(this.baseUrl, { params })
.pipe(
tap(() => this._loading.set(false)),
catchError((error: HttpErrorResponse) => {
this._loading.set(false);
console.error('Error loading articles:', error.message);
return of({ data: [], total: 0, page: 1 });
}),
);
}
getById(id: number) {
return this.http.get<Article>(`${this.baseUrl}/${id}`);
}
create(article: Omit<Article, 'id'>) {
return this.http.post<Article>(this.baseUrl, article);
}
update(id: number, changes: Partial<Article>) {
return this.http.patch<Article>(
`${this.baseUrl}/${id}`,
changes,
);
}
remove(id: number) {
return this.http.delete<void>(`${this.baseUrl}/${id}`);
}
}
Sign in to track your progress