In the HTTP lessons you subscribed to Observables and tracked loading and error by hand. The resource API does that bookkeeping for you and plugs straight into signals. When a signal your request depends on changes, the data reloads automatically.
What you will learn
- How to use
httpResource()for simple HTTP reads - How to use
resource()for any async function - How to show loading and error states and reload on demand
httpResource: a signal-driven GET
// src/app/advanced/post-viewer.ts
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface Post {
id: number;
title: string;
body: string;
}
@Component({
selector: 'app-post-viewer',
template: `
<label for="id">Post number</label>
<input id="id" type="number" min="1" max="100" [value]="postId()" (input)="onInput($event)" />
<button type="button" (click)="post.reload()">Reload</button>
@if (post.isLoading()) {
<p role="status">Loading...</p>
} @else if (post.error()) {
<p role="alert">Could not load the post.</p>
} @else if (post.hasValue()) {
<h2>{{ post.value().title }}</h2>
<p>{{ post.value().body }}</p>
}
`,
})
export class PostViewer {
protected readonly postId = signal(1);
// The URL function reads postId(), so a new id triggers a new request
// and cancels the old one.
protected readonly post = httpResource<Post>(
() => `https://jsonplaceholder.typicode.com/posts/${this.postId()}`,
);
protected onInput(event: Event): void {
const n = Number((event.target as HTMLInputElement).value);
if (Number.isInteger(n) && n >= 1) {
this.postId.set(n);
}
}
}
A resource exposes signals: value(), status(), error(), isLoading() and hasValue(), plus a reload() method. httpResource needs provideHttpClient() in your app config, which you added in the HTTP lesson.
Return undefined from the URL function to skip the request, for example when an input is empty.
resource(): any async source
resource() works with any function that returns a promise, not only HTTP. It takes params (a reactive value) and a loader:
// src/app/advanced/country-search.ts
import { Component, resource, signal } from '@angular/core';
interface Country {
name: { common: string };
}
@Component({
selector: 'app-country-search',
template: `
<label for="q">Country</label>
<input id="q" (input)="query.set($any($event.target).value)" />
@switch (results.status()) {
@case ('idle') { <p>Type at least 2 letters.</p> }
@case ('loading') { <p role="status">Searching...</p> }
@case ('error') { <p role="alert">Search failed.</p> }
@default {
<ul>
@for (c of results.value(); track c.name.common) {
<li>{{ c.name.common }}</li>
}
</ul>
}
}
`,
})
export class CountrySearch {
protected readonly query = signal('');
protected readonly results = resource({
// Returning undefined keeps the resource idle.
params: () => (this.query().trim().length >= 2 ? this.query().trim() : undefined),
loader: async ({ params, abortSignal }) => {
const response = await fetch(
`https://restcountries.com/v3.1/name/${encodeURIComponent(params)}?fields=name`,
{ signal: abortSignal },
);
if (!response.ok) {
throw new Error('Request failed: ' + response.status);
}
return (await response.json()) as Country[];
},
});
}
The abortSignal is cancelled when params changes, so an old slow request cannot replace a newer one. This is the same safety that switchMap gave you with Observables.
Resources vs RxJS
- Use resource / httpResource for "load data that depends on signals" and for reads that show in a template.
- Use HttpClient with Observables for writes (POST, PUT, DELETE), and for streams, debouncing, retries and combining sources.
- A common mix: a debounced search box built with RxJS produces a signal, and a resource reads that signal.
The key parts
- A resource reloads when the signals read in its
paramsor URL function change. - Always handle
isLoading(),error()and the empty state. - Resources are for reading data. Do not use them to send POST requests.
status()can beidle,loading,reloading,resolved,localorerror.
Try it yourself
- Add a "Next post" button to the post viewer that increases
postId. - Make the country search wait 300 ms by debouncing the query signal with RxJS interop.
- Show the previous post while the next one is loading, using
status() === 'reloading'.
Quick check
- What makes an
httpResourcesend a new request? - Which request type should you not use a resource for?
- How does a resource avoid old responses overwriting new ones?
Next, extend templates themselves with custom directives. Read more in the resource guide.