HttpClient: loading data
HttpClient is Angular's built-in client for server requests. It turns a JSON response into objects, can add headers and works well with tests. From this lesson on we build a blog that reads posts from the test API https://jsonplaceholder.typicode.com.
Setup
The client is enabled in the application config with provideHttpClient():
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
],
};Your file's providers may contain other lines — keep them and add provideHttpClient(). In newer Angular versions the client may be available even without this line, but you will need it for settings such as interceptors.
The data type
Describe what a post looks like in the file src/app/post.ts:
// src/app/post.ts
export interface Post {
userId: number;
id: number;
title: string;
body: string;
}A service for requests
API requests are best kept in a service rather than in components:
ng generate service post-api// src/app/post-api.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Post } from './post';
@Injectable({ providedIn: 'root' })
export class PostApi {
private http = inject(HttpClient);
private url = 'https://jsonplaceholder.typicode.com/posts';
getPosts() {
return this.http.get<Post[]>(this.url);
}
getPost(id: number) {
return this.http.get<Post>(`${this.url}/${id}`);
}
}The get<Post[]>() method states which type the response will have. It does not send the request yet — it returns an Observable, an object for a "future response". The request goes out when someone subscribes to it.
Showing it in a component
ng generate component post-list// src/app/post-list/post-list.ts
import { Component, OnInit, inject, signal } from '@angular/core';
import { PostApi } from '../post-api';
import { Post } from '../post';
@Component({
selector: 'app-post-list',
templateUrl: './post-list.html',
})
export class PostList implements OnInit {
private api = inject(PostApi);
posts = signal<Post[]>([]);
ngOnInit() {
this.api.getPosts().subscribe((posts) => this.posts.set(posts));
}
}<!-- src/app/post-list/post-list.html -->
<h2>Posts</h2>
@for (post of posts(); track post.id) {
<article>
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
</article>
} @empty {
<p>Loading...</p>
}The subscribe() method sends the request and calls the function when the response arrives. Add <app-post-list /> to src/app/app.html and PostList to the App component's imports — a second later "Loading..." is replaced by 100 posts. The texts in this API are in Latin, by design.
Common mistake
Calling this.api.getPosts() without subscribe() and waiting for data. Nothing happens: the Network tab in the developer tools shows no request at all. An Observable from HttpClient is lazy — without a subscription the request is not sent.