Skip to main content

Overview

The HttpClient service provides a powerful and flexible API for making HTTP requests in Angular applications. It returns RxJS Observables, enabling reactive programming patterns and composable data streams.

Setup

Standalone applications

Provide HttpClient in your application configuration:
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient()
  ]
};

Module-based applications

Import HttpClientModule in your application module:
app.module.ts
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [HttpClientModule],
  // ...
})
export class AppModule { }
For server-side rendering applications, use withFetch() for better performance:
provideHttpClient(withFetch())

Injecting HttpClient

Inject HttpClient into your services or components:
user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

interface User {
  id: number;
  name: string;
  email: string;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private apiUrl = 'https://api.example.com';

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(`${this.apiUrl}/users`);
  }
}

HTTP Methods

GET requests

Retrieve data from the server:
// Simple GET request
http.get<User[]>('/api/users').subscribe(users => {
  console.log('Users:', users);
});

// GET with query parameters
http.get<User[]>('/api/users', {
  params: { role: 'admin', active: true }
}).subscribe(users => {
  console.log('Admin users:', users);
});

// GET with headers
http.get<User>('/api/users/1', {
  headers: { 'Authorization': 'Bearer token' }
}).subscribe(user => {
  console.log('User:', user);
});

POST requests

Send data to create new resources:
const newUser: User = {
  id: 0,
  name: 'John Doe',
  email: 'john@example.com'
};

http.post<User>('/api/users', newUser).subscribe({
  next: (user) => console.log('Created user:', user),
  error: (error) => console.error('Error:', error)
});

// POST with options
http.post<User>('/api/users', newUser, {
  headers: { 'Content-Type': 'application/json' },
  observe: 'response'
}).subscribe(response => {
  console.log('Status:', response.status);
  console.log('User:', response.body);
});

PUT requests

Update existing resources:
const updatedUser: User = {
  id: 1,
  name: 'Jane Doe',
  email: 'jane@example.com'
};

http.put<User>('/api/users/1', updatedUser).subscribe({
  next: (user) => console.log('Updated user:', user),
  error: (error) => console.error('Update failed:', error)
});

PATCH requests

Partially update resources:
// Update only the name field
http.patch<User>('/api/users/1', { name: 'New Name' }).subscribe(user => {
  console.log('Patched user:', user);
});

DELETE requests

Remove resources:
http.delete(`/api/users/${userId}`).subscribe({
  next: () => console.log('User deleted'),
  error: (error) => console.error('Delete failed:', error)
});

// DELETE with response body
http.delete<{ message: string }>(`/api/users/${userId}`).subscribe(result => {
  console.log(result.message);
});

Request Options

Query parameters

Add URL query parameters using the params option:
import { HttpParams } from '@angular/common/http';

// Using object notation
http.get('/api/users', {
  params: { 
    page: 1, 
    limit: 10,
    sort: 'name'
  }
});

// Using HttpParams for complex scenarios
let params = new HttpParams()
  .set('page', '1')
  .set('limit', '10')
  .append('tags', 'angular')
  .append('tags', 'typescript');

http.get('/api/posts', { params });
// URL: /api/posts?page=1&limit=10&tags=angular&tags=typescript

Headers

Customize request headers:
import { HttpHeaders } from '@angular/common/http';

// Using object notation
http.get('/api/users', {
  headers: {
    'Authorization': 'Bearer token',
    'X-Custom-Header': 'value'
  }
});

// Using HttpHeaders
const headers = new HttpHeaders()
  .set('Authorization', 'Bearer token')
  .set('Content-Type', 'application/json');

http.post('/api/users', data, { headers });

Response types

Specify the expected response type:
// JSON (default)
http.get<User>('/api/users/1');

// Text
http.get('/api/data', { responseType: 'text' }).subscribe(text => {
  console.log(text);
});

// ArrayBuffer
http.get('/api/file', { responseType: 'arraybuffer' }).subscribe(buffer => {
  const blob = new Blob([buffer]);
});

// Blob
http.get('/api/image', { responseType: 'blob' }).subscribe(blob => {
  const url = URL.createObjectURL(blob);
});

Observing responses

Control what information is returned:
// Body only (default)
http.get<User>('/api/users/1').subscribe(user => {
  console.log(user);
});

// Full response with headers and status
http.get<User>('/api/users/1', { observe: 'response' }).subscribe(response => {
  console.log('Status:', response.status);
  console.log('Headers:', response.headers.get('X-Custom'));
  console.log('Body:', response.body);
});

// Event stream for progress tracking
http.get('/api/large-file', { 
  observe: 'events',
  reportProgress: true 
}).subscribe(event => {
  if (event.type === HttpEventType.DownloadProgress) {
    const progress = event.loaded / (event.total || 1) * 100;
    console.log(`Download: ${progress}%`);
  }
});

Working with Observables

Basic subscription

import { Component, OnInit, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-users',
  template: `
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
  `
})
export class UsersComponent implements OnInit {
  private http = inject(HttpClient);
  users: User[] = [];

  ngOnInit() {
    this.http.get<User[]>('/api/users').subscribe({
      next: (users) => this.users = users,
      error: (error) => console.error('Error loading users:', error)
    });
  }
}

RxJS operators

Transform HTTP responses using RxJS operators:
import { map, filter, catchError, retry, timeout } from 'rxjs/operators';
import { of } from 'rxjs';

// Transform the response
http.get<User[]>('/api/users').pipe(
  map(users => users.filter(u => u.active)),
  map(users => users.sort((a, b) => a.name.localeCompare(b.name)))
).subscribe(sortedActiveUsers => {
  console.log(sortedActiveUsers);
});

// Retry on failure
http.get<User[]>('/api/users').pipe(
  retry(3),
  catchError(error => {
    console.error('Failed after 3 retries:', error);
    return of([]); // Return empty array as fallback
  })
).subscribe(users => {
  console.log(users);
});

// Timeout
http.get<User[]>('/api/users').pipe(
  timeout(5000),
  catchError(error => {
    console.error('Request timed out');
    return of([]);
  })
).subscribe(users => {
  console.log(users);
});

Combining multiple requests

import { forkJoin, combineLatest } from 'rxjs';

// Wait for all requests to complete
forkJoin({
  users: http.get<User[]>('/api/users'),
  posts: http.get<Post[]>('/api/posts'),
  comments: http.get<Comment[]>('/api/comments')
}).subscribe(({ users, posts, comments }) => {
  console.log('All data loaded:', users, posts, comments);
});

// Combine latest values
combineLatest([
  http.get<User[]>('/api/users'),
  http.get<Config>('/api/config')
]).subscribe(([users, config]) => {
  console.log('Users and config:', users, config);
});

Sequential requests

import { switchMap, mergeMap } from 'rxjs/operators';

// Switch to new request (cancels previous)
http.get<User>('/api/users/1').pipe(
  switchMap(user => http.get<Post[]>(`/api/users/${user.id}/posts`))
).subscribe(posts => {
  console.log('User posts:', posts);
});

// Process all requests
http.get<User[]>('/api/users').pipe(
  mergeMap(users => users),
  mergeMap(user => http.get<Post[]>(`/api/users/${user.id}/posts`))
).subscribe(posts => {
  console.log('Posts:', posts);
});

Type safety

Provide type information for responses:
interface ApiResponse<T> {
  data: T;
  status: string;
  timestamp: number;
}

interface User {
  id: number;
  name: string;
  email: string;
}

// Type-safe request
http.get<ApiResponse<User>>('/api/users/1').subscribe(response => {
  const user = response.data;
  console.log(user.name); // TypeScript knows about .name
});

// Array of users
http.get<User[]>('/api/users').subscribe(users => {
  users.forEach(user => {
    console.log(user.email); // Type-safe access
  });
});

Advanced options

Request credentials

// Include credentials in cross-origin requests
http.get('/api/users', {
  withCredentials: true
}).subscribe();

// Using Fetch API credentials option
http.get('/api/users', {
  credentials: 'include' // 'omit' | 'same-origin' | 'include'
}).subscribe();

Request timeout

// Set timeout in milliseconds
http.get('/api/users', {
  timeout: 5000 // 5 seconds
}).subscribe({
  error: (error) => console.error('Request timed out')
});

Transfer cache (SSR)

// Cache responses for server-side rendering
http.get('/api/users', {
  transferCache: true
}).subscribe();

// Cache with specific headers
http.get('/api/users', {
  transferCache: {
    includeHeaders: ['Authorization', 'X-Custom-Header']
  }
}).subscribe();

Best practices

Use services for HTTP callsEncapsulate HTTP logic in dedicated services rather than making calls directly from components.
Provide type informationAlways specify generic types for type-safe responses: http.get<User[]>('/api/users')
Handle errors appropriatelyUse error handlers or the catchError operator to gracefully handle failed requests.
HTTP requests are not executed until you subscribe to the Observable. Always remember to subscribe, or use operators like toPromise() or the async pipe in templates.
Unsubscribe when neededFor long-lived subscriptions in components, unsubscribe in ngOnDestroy or use the async pipe which automatically unsubscribes.

Next steps

Interceptors

Transform requests and responses globally with HTTP interceptors.

Error handling

Handle HTTP errors and implement retry logic.