Most apps load data from a server. Angular's HttpClient sends HTTP requests and returns Observables, a stream type you will study in the next lessons. Here you load a list of posts from a public test API.
What you will learn
- How to enable
HttpClientwithprovideHttpClient() - How to write a typed data service
- How to show the data with
toSignal
Enable HttpClient
// src/app/app.config.ts
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes, withComponentInputBinding()),
provideHttpClient(withFetch()),
],
};
withFetch() makes HttpClient use the browser's modern fetch API.
A typed service
// src/app/posts/post.ts
export interface Post {
id: number;
userId: number;
title: string;
body: string;
}
// src/app/posts/post-api.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Post } from './post';
@Injectable({ providedIn: 'root' })
export class PostApi {
private readonly http = inject(HttpClient);
private readonly baseUrl = 'https://jsonplaceholder.typicode.com/posts';
getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(this.baseUrl);
}
getPost(id: number): Observable<Post> {
return this.http.get<Post>(`${this.baseUrl}/${id}`);
}
}
http.get<Post[]>() tells TypeScript the shape of the response. It does not check the data at run time, so it describes what you expect the server to send.
Show the posts
// src/app/posts/post-list.ts
import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { PostApi } from './post-api';
@Component({
selector: 'app-post-list',
template: `
<h2>Posts</h2>
@if (posts(); as list) {
<ul>
@for (post of list; track post.id) {
<li>
<strong>{{ post.title }}</strong>
</li>
}
</ul>
} @else {
<p>Loading posts...</p>
}
`,
})
export class PostList {
private readonly api = inject(PostApi);
// Subscribes for you and unsubscribes when the component is destroyed.
protected readonly posts = toSignal(this.api.getPosts());
}
// src/app/app.ts
import { Component } from '@angular/core';
import { PostList } from './posts/post-list';
@Component({
imports: [PostList],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<h1>Latest posts</h1>
<app-post-list />
</main>
To keep the routing demo, you can add a posts route to app.routes.ts instead of replacing app.html. Either way, run ng serve and the posts appear after a moment.
The key parts
- An Observable does nothing until something subscribes to it.
toSignalsubscribes for you. toSignalreturnsundefineduntil the first value arrives, which is why the template shows "Loading..." in the@elsebranch.- Put HTTP code in a service, not in components, so it can be reused and tested.
HttpClientautomatically parses JSON.
Try it yourself
- Show the first 100 characters of each post body.
- Add a post detail page that uses
getPost(id)and a route parameter. - Open the Network tab and find the request.
Quick check
- Which function enables
HttpClient? - When does an Observable send its request?
- Why put HTTP calls in a service?
Real requests are slow and sometimes fail. Next, handle loading and errors, and send data with POST. See the HttpClient guide for more.