Angular's HttpClient service, part of @angular/common/http, provides a simplified API for making HTTP requests. It returns typed Observables, supports request/response interception, and includes features like progress events and JSONP.
Import HttpClientModule into your root or feature module:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [HttpClientModule],
// ...
})
export class AppModule { }
For standalone apps, use provideHttpClient() in the providers array:
// main.ts or app.config.ts
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideHttpClient()]
};
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
interface Product {
id: number;
name: string;
price: number;
}
@Injectable({ providedIn: 'root' })
export class ProductService {
private apiUrl = 'https://api.example.com/products';
constructor(private http: HttpClient) { }
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl);
}
}
createProduct(product: Product): Observable<Product> {
return this.http.post<Product>(this.apiUrl, product);
}
updateProduct(id: number, product: Product): Observable<Product> {
return this.http.put<Product>(`${this.apiUrl}/${id}`, product);
}
deleteProduct(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
Add query parameters to requests:
import { HttpParams } from '@angular/common/http';
searchProducts(query: string, page: number): Observable<Product[]> {
const params = new HttpParams()
.set('q', query)
.set('page', page.toString())
.set('limit', '20');
return this.http.get<Product[]>(this.apiUrl, { params });
}
Set custom headers for requests:
import { HttpHeaders } from '@angular/common/http';
getHeaders(): HttpHeaders {
return new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.auth.getToken()}`,
'X-Custom-Header': 'custom-value'
});
}
getSecuredData(): Observable<any> {
return this.http.get(this.apiUrl, { headers: this.getHeaders() });
}
HttpClient methods return cold Observables. The request is made only when you subscribe:
this.productService.getProducts().subscribe({
next: (products) => this.products = products,
error: (err) => this.errorMessage = 'Failed to load products',
complete: () => this.loading = false
});
Use RxJS operators like catchError and retry for robust error handling:
import { catchError, retry, throwError } from 'rxjs';
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl).pipe(
retry(2), // Retry failed requests up to 2 times
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
let errorMsg = 'An unknown error occurred';
if (error.error instanceof ErrorEvent) {
errorMsg = `Client error: ${error.error.message}`;
} else {
errorMsg = `Server error: ${error.status} - ${error.message}`;
}
console.error(errorMsg);
return throwError(() => new Error(errorMsg));
}
Interceptors examine and transform HTTP requests and responses globally. They are useful for adding auth tokens, logging, caching, or error handling.
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.auth.getToken();
const authReq = token
? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
: req;
return next.handle(authReq);
}
}
// Provide the interceptor
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const start = Date.now();
return next.handle(req).pipe(
tap(() => console.log(`${req.method} ${req.url} took ${Date.now() - start}ms`))
);
}
}
Upload files using FormData with POST:
uploadFile(file: File): Observable<any> {
const formData = new FormData();
formData.append('file', file, file.name);
return this.http.post('/api/upload', formData, {
reportProgress: true,
observe: 'events'
});
}
Subscribe with progress tracking:
this.uploadService.uploadFile(file).subscribe(event => {
if (event.type === HttpEventType.UploadProgress) {
const percent = Math.round(100 * event.loaded / event.total);
this.progress = percent;
} else if (event.type === HttpEventType.Response) {
console.log('Upload complete', event.body);
}
});
For cross-origin requests that don't support CORS, use HttpClient.jsonp():
this.http.jsonp('https://api.example.com/callback?format=jsonp', 'callback')
.subscribe(data => console.log(data));
You learned how to use Angular's HttpClient for full CRUD operations, configure HttpParams and HttpHeaders, handle errors with catchError and retry, implement request/response interceptors for auth and logging, and handle file uploads with progress tracking. These HTTP patterns are essential for any real-world Angular application.
TodoService with full CRUD operations against JSONPlaceholder (https://jsonplaceholder.typicode.com/todos). Implement a logging interceptor that logs all request URLs and durations. Add a retry of 1 for failed requests. Build a TodoComponent that displays todos, marks them as complete (PUT), adds new todos (POST), and deletes them (DELETE) with error handling using catchError.