Angular · Data

HttpClient

HttpClient is Angular’s built-in way to call APIs. It returns typed Observables, plugs into interceptors, and works nicely with signals.

Intermediate 6 min Lesson 22 of 38Examples target Angular 20 with standalone components. Each lesson states the version it was verified against.
Before you start:ServicesObservables
0/38 in Angular
On this page · 6 min · Intermediate

Learning objectives#

  • Enable HttpClient with provideHttpClient
  • Send typed GET, POST, PUT and DELETE requests
  • Pass query parameters and headers
  • Expose results as signals and handle errors

Explanation#

Add provideHttpClient() (optionally withFetch() and withInterceptors([...])) to app.config.ts, then inject(HttpClient) in a service.

Requests are typed: http.get<Product[]>("/api/products"). Nothing is sent until something subscribes — the async pipe, toSignal, or .subscribe().

Options: { params: { page: 2, q: "pen" } } builds the query string; { headers: {...} } adds headers. post(url, body) sends JSON automatically.

toSignal(observable, { initialValue: [] }) turns a request into a signal you can read in templates without manual subscriptions.

Failed requests (network errors, 4xx/5xx) error the Observable with an HttpErrorResponse (status, error body). Handle them with catchError or in an interceptor.

Keep HTTP calls in services so components only call productApi.list().

Syntax#

TypeScript Illustrative
// app.config.ts
providers: [provideHttpClient(withFetch())]


// product-api.ts
private http = inject(HttpClient);
list(q = '') { return this.http.get<Product[]>('/api/products', { params: { q } }); }
create(p: NewProduct) { return this.http.post<Product>('/api/products', p); }

Code example: A typed API service and a component using toSignal#

TypeScripttodo-api.ts Illustrative
import { Component, Injectable, inject, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { catchError, of } from 'rxjs';


export type Todo = { id: number; title: string; completed: boolean };


@Injectable({ providedIn: 'root' })
export class TodoApi {
  private http = inject(HttpClient);
  private base = 'https://jsonplaceholder.typicode.com/todos';


  list(limit = 5) {
    return this.http.get<Todo[]>(this.base, { params: { _limit: limit } });
  }


  create(title: string) {
    return this.http.post<Todo>(this.base, { title, completed: false });
  }


  remove(id: number) {
    return this.http.delete<void>(`${this.base}/${id}`);
  }
}


@Component({
  selector: 'app-todos',
  template: `
    @if (error()) { <p role="alert">{{ error() }}</p> }
    <ul>
      @for (t of todos(); track t.id) { <li>{{ t.completed ? '✅' : '⬜' }} {{ t.title }}</li> }
      @empty { <li>Loading…</li> }
    </ul>
    <button (click)="add()">Add “Learn HttpClient”</button>
    @if (created()) { <p>Created #{{ created()!.id }}: {{ created()!.title }}</p> }
  `,
})
export class Todos {
  private api = inject(TodoApi);
  error = signal('');
  created = signal<Todo | null>(null);


  todos = toSignal(
    this.api.list().pipe(
      catchError((err: HttpErrorResponse) => {
        this.error.set(`Could not load todos (${err.status || 'network error'}).`);
        return of([] as Todo[]);
      }),
    ),
    { initialValue: [] as Todo[] },
  );


  add() {
    this.api.create('Learn HttpClient').subscribe({
      next: todo => this.created.set(todo),
      error: () => this.error.set('Could not create the todo.'),
    });
  }
}
// app.config.ts: providers: [provideHttpClient(withFetch())]
Expected output
The list shows “Loading…”, then five todos with ✅/⬜ marks. Clicking the button POSTs JSON and shows “Created #201: Learn HttpClient” (the test API echoes it back). If the network is down, “Could not load todos (network error).” appears instead of crashing.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. TodoApi wraps every endpoint; get<Todo[]> types the response.
  2. params: { _limit: 5 } becomes ?_limit=5.
  3. toSignal subscribes to the request and exposes the result as a signal with an initial empty array.
  4. catchError turns an HTTP error into a friendly message and an empty list.
  5. post sends the object as JSON; subscribe triggers the request.

Real-world use case#

An Angular admin talks to an ASP.NET Core API through one service per resource (ProductApi, OrderApi). Components never build URLs, and an interceptor adds the auth token to every request.

Common mistakes#

NullInjectorError: No provider for HttpClient

provideHttpClient() is missing from app.config.ts.

Nothing happens

HTTP Observables are cold: without subscribe, async or toSignal, no request is sent.

Subscribing in loops or templates

Calling api.list() in a template expression creates a new request on every change detection. Assign it once to a field or signal.

Best practices#

  • provideHttpClient(withFetch()) in app.config
  • Typed requests in API services
  • toSignal or async pipe instead of manual subscriptions
  • Handle errors where you can show a message

Remember this#

provideHttpClient() once; inject(HttpClient) in services.

No subscriber → no request.

toSignal(http.get(...), { initialValue }) for templates.

Practice exercise#

Your task: Add a search(q: string) method to a ProductApi that calls GET /api/products?q=… and returns Observable<Product[]>.

Need a hint?
this.http.get<Product[]>("/api/products", { params: { q } }).
Show solution
TypeScript Illustrative
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';


export type Product = { id: number; name: string; price: number };


@Injectable({ providedIn: 'root' })
export class ProductApi {
  private http = inject(HttpClient);
  search(q: string) {
    return this.http.get<Product[]>('/api/products', { params: { q } });
  }
}

Mini quiz#

HttpClient quizMultiple choice Question 1 of 3

When does http.get(...) send the request?

Score so far: 0/0

Want more? Practise all Angular MCQs · Angular interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.