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.
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