Angular HTTP Client
Angular's HttpClient provides a typed, Observable-based API for talking to REST endpoints, covering everything from simple GET requests to POST submissions with error handling.
Setting Up HttpClient
In a standalone application, HttpClient is enabled by adding provideHttpClient() to the providers array in your application config. This registers the client and lets you opt into features like fetch-based requests or interceptors as separate, composable functions.
app.config.ts
import { Component, ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { bootstrapApplication } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch())
]
};
@Component({
selector: 'app-root',
standalone: true,
template: `<p>HttpClient is configured via provideHttpClient(withFetch()).</p>`
})
export class AppComponent {}
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
Making GET Requests
Inject HttpClient into a service and call .get<T>(url) to request data. The call returns an Observable, not a Promise — nothing happens until something subscribes to it. Generic type parameters let TypeScript check that the rest of your code uses the response shape correctly.
user.service.ts
import { Injectable, inject, Component } from '@angular/core';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { bootstrapApplication } from '@angular/platform-browser';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private apiUrl = 'https://api.example.com/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUser(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}
@Component({
selector: 'app-root',
standalone: true,
template: `<p>{{ status }}</p>`
})
export class AppComponent {
status = 'Loading users...';
constructor(private userService: UserService) {
this.userService.getUsers().subscribe({
next: (users) => {
this.status = `Loaded ${users.length} users`;
console.log('Users:', users);
},
error: (err) => {
this.status = 'Failed to load users';
console.error('Failed to load users', err);
}
});
}
}
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()]
}).catch((err) => console.error(err));
Making POST Requests and Handling Errors
POST requests take a body as the second argument. Pipe the Observable through RxJS operators such as catchError to intercept failures, log them, and rethrow a friendlier error for the calling component to display.
comment.service.ts
import { Injectable, inject, Component } from '@angular/core';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';
import { bootstrapApplication } from '@angular/platform-browser';
export interface Comment {
postId: number;
body: string;
}
@Injectable({ providedIn: 'root' })
export class CommentService {
private http = inject(HttpClient);
addComment(comment: Comment): Observable<Comment> {
return this.http.post<Comment>('https://api.example.com/comments', comment).pipe(
catchError(err => {
console.error('Failed to post comment', err);
return throwError(() => new Error('Could not save comment'));
})
);
}
}
@Component({
selector: 'app-root',
standalone: true,
template: `<p>{{ status }}</p>`
})
export class AppComponent {
status = 'Posting comment...';
constructor(private commentService: CommentService) {
this.commentService.addComment({ postId: 1, body: 'Great post!' }).subscribe({
next: (comment) => {
this.status = 'Comment posted successfully';
console.log('Posted comment:', comment);
},
error: (err) => {
this.status = 'Could not save comment';
console.error(err);
}
});
}
}
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()]
}).catch((err) => console.error(err));
- HttpClient Observables are cold — the HTTP request is only sent once you call .subscribe().
- Each subscription triggers its own independent request; subscribing twice sends the request twice.
- The async pipe in a template subscribes and unsubscribes for you automatically.
- Manual subscriptions in a component should be cleaned up, or scoped with takeUntilDestroyed(), to avoid leaks.
Exercise: Angular HTTP Client
Which Angular service makes HTTP requests to a backend API?