AngularJSHttpClient

HttpClient in Angular

Angular's HttpClient is the built-in service for making HTTP requests. It wraps the browser's XMLHttpRequest / fetch API, adds TypeScript generics, and returns RxJS Observables so you can compose, transform, and cancel requests with operators.

HttpClient lives in @angular/common/http and must be provided before use.

Setting Up HttpClient

In modern standalone Angular (v15+) call provideHttpClient() in your app.config.ts.

TS
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withFetch()), // use the modern Fetch API under the hood
  ],
};
Note
withFetch() switches the underlying transport to the browser Fetch API. Omit it to use the legacy XMLHttpRequest transport.
NgModule Setup (Older Projects)

TS
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [HttpClientModule],
})
export class AppModule {}
Making GET Requests

TS
// src/app/services/user.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private apiUrl = 'https://jsonplaceholder.typicode.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}`);
  }
}
Consuming the Observable in a Component

TS
// src/app/users/users.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { UserService, User } from '../services/user.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-users',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    @if (users$ | async; as users) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }} — {{ user.email }}</li>
        }
      </ul>
    }
  `,
})
export class UsersComponent {
  private userService = inject(UserService);
  users$: Observable<User[]> = this.userService.getUsers();
}
Tip
Using the async pipe automatically subscribes and unsubscribes — no need to manage subscriptions manually or call unsubscribe().
POST, PUT, PATCH, DELETE

TS
// Full CRUD service example
@Injectable({ providedIn: 'root' })
export class PostService {
  private http = inject(HttpClient);
  private url = 'https://jsonplaceholder.typicode.com/posts';

  // POST — create
  createPost(body: Partial<Post>): Observable<Post> {
    return this.http.post<Post>(this.url, body);
  }

  // PUT — full replace
  updatePost(id: number, body: Post): Observable<Post> {
    return this.http.put<Post>(`${this.url}/${id}`, body);
  }

  // PATCH — partial update
  patchPost(id: number, changes: Partial<Post>): Observable<Post> {
    return this.http.patch<Post>(`${this.url}/${id}`, changes);
  }

  // DELETE
  deletePost(id: number): Observable<void> {
    return this.http.delete<void>(`${this.url}/${id}`);
  }
}
Request Options: Headers, Params, and Observe

TS
import { HttpHeaders, HttpParams } from '@angular/common/http';

// Custom headers
const headers = new HttpHeaders({
  'Authorization': 'Bearer my-token',
  'Content-Type': 'application/json',
});

// Query parameters  (?page=2&limit=20)
const params = new HttpParams()
  .set('page', 2)
  .set('limit', 20);

// Request with both
this.http.get<Post[]>(this.url, { headers, params }).subscribe(console.log);

// observe: 'response' — get the full HttpResponse including status/headers
this.http.get<Post[]>(this.url, { observe: 'response' }).subscribe((res) => {
  console.log('Status:', res.status);
  console.log('Body:', res.body);
  console.log('X-Total-Count:', res.headers.get('X-Total-Count'));
});

// observe: 'events' — track upload/download progress
this.http.post(this.url, body, { observe: 'events', reportProgress: true })
  .subscribe((event) => {
    if (event.type === HttpEventType.UploadProgress) {
      const percent = Math.round(100 * event.loaded / (event.total ?? 1));
      console.log(`Upload: ${percent}%`);
    }
  });
Error Handling

Use RxJS catchError to handle HTTP errors gracefully. The error is an HttpErrorResponse object with status, message, and error properties.

TS
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class DataService {
  private http = inject(HttpClient);

  getData(): Observable<Data[]> {
    return this.http.get<Data[]>('/api/data').pipe(
      catchError((error: HttpErrorResponse) => {
        let message = 'An unknown error occurred';

        if (error.status === 0) {
          message = 'Network error — check your connection';
        } else if (error.status === 401) {
          message = 'Unauthorized — please log in again';
        } else if (error.status === 404) {
          message = 'Resource not found';
        } else if (error.status >= 500) {
          message = 'Server error — try again later';
        }

        console.error('HTTP Error:', error);
        return throwError(() => new Error(message));
      })
    );
  }
}
Retry Logic

TS
import { retry, catchError } from 'rxjs/operators';

getData(): Observable<Data[]> {
  return this.http.get<Data[]>('/api/data').pipe(
    retry({ count: 3, delay: 1000 }), // retry 3 times, 1 second apart
    catchError(this.handleError)
  );
}
Cancelling Requests with takeUntil

TS
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({ selector: 'app-search', standalone: true, template: '' })
export class SearchComponent implements OnDestroy {
  private destroy$ = new Subject<void>();
  private http = inject(HttpClient);

  search(term: string): void {
    this.http.get<Result[]>(`/api/search?q=${term}`)
      .pipe(takeUntil(this.destroy$))
      .subscribe((results) => {
        // handle results
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }
}
Tip
With signals-based components (Angular 17+) use \`takeUntilDestroyed()\` from \`@angular/core/rxjs-interop\` — it hooks into the component's destroy lifecycle automatically.
Typed HTTP with Interfaces

TS
// Always type your HTTP calls — generics give you compile-time safety
export interface ApiResponse<T> {
  data: T;
  total: number;
  page: number;
}

export interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

@Injectable({ providedIn: 'root' })
export class ProductService {
  private http = inject(HttpClient);

  getProducts(page = 1): Observable<ApiResponse<Product[]>> {
    const params = new HttpParams().set('page', page);
    return this.http.get<ApiResponse<Product[]>>('/api/products', { params });
  }
}
File Uploads

TS
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',
  }).pipe(
    map((event) => {
      switch (event.type) {
        case HttpEventType.UploadProgress:
          return { progress: Math.round(100 * event.loaded / (event.total ?? 1)) };
        case HttpEventType.Response:
          return { done: true, body: event.body };
        default:
          return {};
      }
    })
  );
}
HttpClient Feature Comparison

Feature

API

GET request

http.get<T>(url, options)

POST request

http.post<T>(url, body, options)

Custom headers

new HttpHeaders({ key: value })

Query params

new HttpParams().set(key, value)

Full response

{ observe: 'response' }

Progress events

{ reportProgress: true, observe: 'events' }

Error handling

catchError((err: HttpErrorResponse) => ...)

Retry on fail

retry({ count: N, delay: ms })

Best Practices
  • Always type HTTP calls with generics: http.get<User[]>(url)

  • Put HTTP logic in services, not components

  • Use catchError in every service method that makes network calls

  • Unsubscribe from long-lived subscriptions with takeUntil or takeUntilDestroyed

  • Use the async pipe in templates to avoid manual subscription management

  • Use HttpParams instead of string concatenation for query parameters

  • Set withFetch() in provideHttpClient() for modern fetch-based transport

  • Use interceptors for cross-cutting concerns like auth tokens and logging

Summary

HttpClient is the standard Angular API for all HTTP communication. Its Observable-based interface integrates naturally with RxJS operators for transformation, error handling, retry logic, and cancellation. Typed generics catch response-shape errors at compile time, and interceptors keep auth and logging concerns out of your service layer.