HttpClient in Angular

Call your Web API from a service with HttpClient's get, post, put and delete, each typed with the model it returns. Angular provides HttpClient on its own. While developing, a proxy sends the app's /api requests on to the API, so the browser sees one origin.

Journeyman · Angular · REST API · Updated

After this lesson you can

  • Call a Web API from a service with HttpClient's get, post, put and delete
  • Send an Angular app's /api requests to the Web API while developing, with a proxy

Key takeaways

  • HttpClient is ready to inject with no setup; add provideHttpClient() only to change how it works, for example to give it interceptors.
  • Put API calls in a service, one per resource, such as CategoryApi, so components never build URLs themselves.
  • get, post, put and delete are typed with what the JSON is, such as get<Category[]>, and post and put send their object as JSON.
  • Each call returns an Observable, and nothing is sent until you subscribe to it.
  • While developing, proxy.conf.json lets ng serve pass /api requests on to the API, so the browser sees one origin and CORS doesn't block the calls.

An Angular app gets its data from a Web API. Angular's way to call one is HttpClient: fetch, made for Angular. These lessons use the categories API you built in HTTP and Web APIs.

Ready to use

HttpClient is a service that Angular provides on its own: there's nothing to set up before you inject it. It sends its requests with the browser's fetch.

Older apps and tutorials add provideHttpClient() to the providers in src/app/app.config.ts. Older versions of Angular needed it. Now you add it only to change how HttpClient works, for example to give it interceptors, code that runs on every request:

import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
    provideHttpClient()
  ]
};

Without options, as here, it changes nothing, so you can leave it out.

A service for the API

API calls go in a service, one per resource, like a client class in A Simple API Client. Components never build URLs themselves.

import { HttpClient } from '@angular/common/http';
import { inject, Service } from '@angular/core';
import { Observable } from 'rxjs';

export interface Category {
  categoryId: number;
  categoryName: string;
  description: string | null;
}

@Service()
export class CategoryApi {
  private readonly http = inject(HttpClient);

  getAll(): Observable<Category[]> {
    return this.http.get<Category[]>('/api/categories');
  }

  getById(id: number): Observable<Category> {
    return this.http.get<Category>(`/api/categories/${id}`);
  }

  add(category: Category): Observable<Category> {
    return this.http.post<Category>('/api/categories', category);
  }

  update(category: Category): Observable<Category> {
    return this.http.put<Category>(`/api/categories/${category.categoryId}`, category);
  }

  delete(id: number): Observable<void> {
    return this.http.delete<void>(`/api/categories/${id}`);
  }
}
  • inject(HttpClient) gets Angular's HTTP client, as a component injects a service.
  • get<Category[]>(url) sends a GET, and the type says what the JSON is: here, a list of categories. post, put and delete work the same way; post and put take the object to send as JSON, with the Content-Type header set for you.
  • description: string | null says the description is a string or null, as the API sends it: TypeScript's way of writing C#'s string?.
  • Each method returns an Observable: a value that arrives later, like a promise. The next lesson explains observables; for now, you subscribe to one to get its value.

Using it in a component

import { Component, inject, OnInit, signal } from '@angular/core';
import { Category, CategoryApi } from './category-api';

@Component({
  imports: [],
  selector: 'app-category-list',
  template: `
    <ul class="list-group">
      @for (category of categories(); track category.categoryId) {
        <li class="list-group-item">{{ category.categoryName }}</li>
      }
    </ul>
  `,
})
export class CategoryList implements OnInit {
  private readonly api = inject(CategoryApi);
  protected readonly categories = signal<Category[]>([]);

  ngOnInit() {
    this.api.getAll().subscribe(categories => this.categories.set(categories));
  }
}

When the component starts, it asks for the categories. subscribe(callback) sends the request, and calls the callback with the list when it arrives; the callback puts it in the signal, and the page shows the eight categories.

A proxy, while developing

The app runs at http://localhost:4200 and the API at, say, http://localhost:5179: different ports, so different origins, and the browser would block the calls. That's the CORS rule from Fetch. The API could say the app's origin is allowed, but while developing there's a simpler way: the Angular dev server can pass requests on. Create proxy.conf.json beside angular.json:

{
  "/api": {
    "target": "http://localhost:5179",
    "secure": false
  }
}

and tell ng serve to use it, in angular.json, under serve:

"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "proxyConfig": "proxy.conf.json"
  },
  ...
}

Restart ng serve. Now a request from the app for /api/categories goes to http://localhost:4200/api/categories, and the dev server passes it on to the API and hands back the answer. As far as the browser knows, there's only one origin. That's why the service uses addresses starting /api/, with no host: the same code works when the app and API are published together.

Start both: the API (Ctrl+F5 in Visual Studio) and the app (ng serve). Open http://localhost:4200, and the categories come from your database.

Check yourself

2 questions to try on the lesson page.

So what

HttpClient, ready to inject, in a service per resource with typed get, post, put and delete, and a proxy while you develop. Components ask the service, and never see a URL.

Key terms

Cheat sheets