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
HttpClientis ready to inject with no setup; addprovideHttpClient()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,putanddeleteare typed with what the JSON is, such asget<Category[]>, andpostandputsend their object as JSON.- Each call returns an
Observable, and nothing is sent until yousubscribeto it. - While developing,
proxy.conf.jsonletsng servepass/apirequests 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 aGET, and the type says what the JSON is: here, a list of categories.post,putanddeletework the same way;postandputtake the object to send as JSON, with theContent-Typeheader set for you.description: string | nullsays the description is a string ornull, as the API sends it: TypeScript's way of writing C#'sstring?.- 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
- HttpClient
- Dev Server Proxy