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.
MethodPurposeTypical use
get<T>(url)Read dataFetching a list or single resource
post<T>(url, body)Create dataSubmitting a new form/record
put<T>(url, body)Replace dataFull update of an existing resource
patch<T>(url, body)Partially update dataUpdating a subset of fields
delete<T>(url)Remove dataDeleting a resource by id
Note: Prefer the async pipe ({{ data$ | async }}) over manual subscribe() calls in components whenever you don't need to react to the result imperatively — it keeps the component free of manual cleanup code.
Note: HttpClient does not retry or cache requests by default. For flaky networks or repeated requests, add the retry() operator or a caching layer explicitly — don't assume Angular handles it for you.

Exercise: Angular HTTP Client

Which Angular service makes HTTP requests to a backend API?