Angular HttpClient
HttpClient lets your app fetch and send data over HTTP.
HTTP Essentials
- Client: Use
HttpClient
to fetch and send JSON. - Observables: HTTP methods return Observables. Use
subscribe()
or theasync
pipe. - UX: Show loading and clear error messages.
- Provide once: Register
provideHttpClient()
at bootstrap.
import { provideHttpClient, HttpClient } from '@angular/common/http';
// Bootstrap
// bootstrapApplication(App, { providers: [provideHttpClient()] });
// Use in a component
// http.get<User[]>('/api/users').subscribe({ next: d => users = d });
Notes:
- Related: See Services to encapsulate HTTP logic, Router for navigations after requests, and Templates for rendering lists and states.
- Provide the client with
provideHttpClient()
at bootstrap. - Use non-blocking UIs with loading and error states; be mindful of CORS (browser security that controls which origins can call an API) when calling public APIs.
- Base URL & env: Centralize the API base URL/config in a token or constant and reuse it in services.
- Security: Validate untrusted URL parts; avoid interpolating raw user input directly into request URLs.
GET Requests
- Read data with
http.get<T>()
. - Track
loading
anderror
state for UX. - Update component state in the subscription.
loading = true; error = '';
http.get<User[]>('/api/users').subscribe({
next: d => { users = d; loading = false; },
error: () => { error = 'Failed to load'; loading = false; }
});
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { provideHttpClient, HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<h3>HttpClient</h3>
<button (click)="load()">Load Users</button>
<p *ngIf="loading">Loading...</p>
<p *ngIf="error" style="color:crimson">{{ error }}</p>
<ul>
<li *ngFor="let u of users">{{ u.name }} ({{ u.email }})</li>
</ul>
`
})
export class App {
http = inject(HttpClient);
users: any[] = [];
loading = false;
error = '';
load() {
this.loading = true;
this.error = '';
this.http.get<any[]>('https://jsonplaceholder.typicode.com/users')
.subscribe({
next: (data) => { this.users = data; this.loading = false; },
error: () => { this.error = 'Failed to load users'; this.loading = false; }
});
}
}
bootstrapApplication(App, { providers: [provideHttpClient()] });
<app-root></app-root>
Example explained
provideHttpClient()
: RegistersHttpClient
so requests work.inject(HttpClient)
: Retrieves the client in a standalone component.- GET:
http.get<any[]>(url)
returns an Observable;subscribe()
setsusers
on success anderror
on failure. - UX flags:
loading
anderror
drive the template (spinner/message).
Notes:
- Standalone-friendly: Use
provideHttpClient()
over importingHttpClientModule
. - Cross-cutting: Use interceptors (hooks on each request/response) for auth/logging; register once at the appropriate scope.
- UX: Avoid multiple clicks by disabling the button while loading.
- CORS: If calls fail in the browser but work in curl, it may be CORS—use APIs that allow your origin or a proxy.
- Typing & options: Use
get<MyType>()
and build options withHttpParams
/HttpHeaders
.
POST Requests
- Create data with
http.post<T>()
. - Disable the button while sending to prevent duplicates.
- Render the returned result or an error message.
loading = true; error = ''; result = null;
http.post<Post>('/api/posts', { title, body }).subscribe({
next: r => { result = r; loading = false; },
error: () => { error = 'Failed to create'; loading = false; }
});
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { provideHttpClient, HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
template: `
<h3>HttpClient POST</h3>
<button (click)="createPost()" [disabled]="loading">Create Post</button>
<p *ngIf="loading">Sending...</p>
<p *ngIf="error" style="color:crimson">{{ error }}</p>
<div *ngIf="result">
<p>Created Post ID: {{ result.id }}</p>
<p>Title: {{ result.title }}</p>
</div>
`
})
export class App {
http = inject(HttpClient);
loading = false;
error = '';
result: any = null;
createPost() {
this.loading = true;
this.error = '';
this.result = null;
this.http.post<any>('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: 'bar',
userId: 1
}).subscribe({
next: (res) => { this.result = res; this.loading = false; },
error: () => { this.error = 'Failed to create post'; this.loading = false; }
});
}
}
bootstrapApplication(App, { providers: [provideHttpClient()] });
<app-root></app-root>
Example explained
- POST:
http.post<T>(url, body)
sends JSON and returns the created object. - Disable while sending: The button binds to
loading
to prevent duplicate submissions. - Result: On success, show the returned
result
; on error, show a clear message.
Notes:
- Validate inputs: Send the minimal, validated payload the server expects; show field errors clearly.
- Idempotency: Avoid duplicate submissions; keep the button disabled while sending and after success if appropriate.
- Handle errors: Show a clear error message and let the user retry; keep the UI responsive with a loading flag.
Error Handling
- Always show a helpful message and allow retry.
- Check
status
codes to decide retry vs fail fast. - Keep the UI responsive with a loading flag.
error = ''; loading = true;
http.get('/api/data').subscribe({
next: r => { data = r; loading = false; },
error: err => { error = `Request failed (status ${'{'}err?.status ?? 'unknown'{}})`; loading = false; }
});
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { provideHttpClient, HttpClient, withInterceptors, HttpResponse, HttpErrorResponse, HttpRequest, HttpHandlerFn } from '@angular/common/http';
import { of, throwError } from 'rxjs';
// Fake HTTP interceptor so the demo works without external network calls
function mockHttp(req: HttpRequest<any>, next: HttpHandlerFn) {
if (req.method === 'GET' && req.url.includes('jsonplaceholder.typicode.com/usersx')) {
return throwError(() => new HttpErrorResponse({ status: 404, statusText: 'Not Found', url: req.url }));
}
if (req.method === 'GET' && req.url.includes('jsonplaceholder.typicode.com/users')) {
const body = [
{ id: 1, name: 'Leanne Graham', email: '[email protected]' },
{ id: 2, name: 'Ervin Howell', email: '[email protected]' }
];
return of(new HttpResponse({ status: 200, body }));
}
return next(req);
}
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
styles: [`
button { margin-right: 8px; }
.error { color: crimson; }
.ok { color: seagreen; }
`],
template: `
<h3>HTTP Error Handling</h3>
<div>
<button (click)="loadOk()" [disabled]="loading">Load OK</button>
<button (click)="loadFail()" [disabled]="loading">Load Fail</button>
<button (click)="retry()" [disabled]="!lastAction || loading">Retry</button>
</div>
<p *ngIf="loading">Loading...</p>
<p *ngIf="error" class="error">{{ error }}</p>
<p *ngIf="!error && data" class="ok">Loaded {{ isArray(data) ? data.length + ' items' : '1 item' }}</p>
<ul *ngIf="isArray(data)">
<li *ngFor="let u of data">{{ u.name }} ({{ u.email }})</li>
</ul>
`
})
export class App {
http = inject(HttpClient);
loading = false;
error = '';
data: any[] | null = null;
lastAction = '';
isArray(value: unknown): value is any[] { return Array.isArray(value as any); }
fetch(url: string): void {
this.loading = true;
this.error = '';
this.data = null;
this.http.get<any[]>(url).subscribe({
next: (res) => { this.data = res; this.loading = false; },
error: (err) => {
const status = err?.status ?? 'unknown';
this.error = `Request failed (status ${status}). Please try again.`;
this.loading = false;
}
});
}
loadOk() {
this.lastAction = 'ok';
this.fetch('https://jsonplaceholder.typicode.com/users');
}
loadFail() {
this.lastAction = 'fail';
this.fetch('https://jsonplaceholder.typicode.com/usersx');
}
retry() {
if (this.lastAction === 'ok') this.loadOk();
else if (this.lastAction === 'fail') this.loadFail();
}
}
bootstrapApplication(App, { providers: [provideHttpClient(withInterceptors([mockHttp]))] });
<app-root></app-root>
Example explained
- Error message: Build a helpful message from
err.status
; keep the UI responsive withloading
. - Retry: Store the
lastAction
and wire aretry()
button to re-run the last request. - OK vs Fail: Separate methods help demonstrate success and failure flows.
Notes:
- Don't swallow errors: Surface a helpful message to users and log details for debugging.
- Retry strategy: Only retry on transient errors (e.g., 5xx); avoid retrying 4xx client errors.
- Feedback: Show a clear error message and let the user retry; keep the UI responsive with a loading flag.
- Cancel stale requests: On rapid input, switch to the latest stream (e.g.,
switchMap
) to avoid races. - Interceptors: Centralize auth headers, logging, and retry to keep components slim.
HTTP Interceptors
- Run cross-cutting logic on every request/response (e.g., auth headers, logging).
- Register once at bootstrap with
provideHttpClient(...)
.
import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const cloned = req.clone({ setHeaders: { Authorization: 'Bearer TOKEN' } });
return next(cloned);
};
bootstrapApplication(App, {
providers: [provideHttpClient(withInterceptors([authInterceptor]))]
});
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
@Component({ selector: 'app-root', standalone: true, template: `<p>Interceptors demo</p>` })
export class App {}
bootstrapApplication(App, {
providers: [provideHttpClient(withInterceptors([]))]
});
<app-root></app-root>
Example explained
withInterceptors([...])
: Registers interceptor functions for all requests.- Interceptor: A function
(req, next)
that canreq.clone(...)
to modify the request, then callsnext(req)
. provideHttpClient()
: Enables HttpClient and composes interceptors at bootstrap.