Resources are the signal-native way to fetch data in Angular, replacing hand-written loading and error flags.
A resource turns asynchronous data into signals. It re-fetches automatically when the signals it depends on change, and it exposes state you can use in templates.
`httpResource` (from @angular/common/http) wraps HttpClient. Pass a function that returns the URL (or a request object); signals read inside it make the resource reactive:
user = httpResource<User>(() => `/api/users/${this.userId()}`);`resource` (from @angular/core) works with any Promise-based loader:
weather = resource({
params: () => ({ city: this.city() }),
loader: ({ params, abortSignal }) => fetch(`/api/weather?city=${params.city}`, { signal: abortSignal }).then((r) => r.json()),
});A resource exposes:
value()— the data (undefineduntil loaded);hasValue()narrows it safely.isLoading(),error(), andstatus()('idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local').reload()to fetch again, andset/updateto change the value locally (for optimistic updates).
When params change while a request is in flight, the previous request is aborted (through abortSignal), so stale responses never win. If the params or URL function returns undefined, the loader doesn't run and the resource stays idle, a neat way to wait for required input.
Use resources for reading data. Mutations (POST/PUT) should be explicit calls in event handlers or form submission, followed by reload() if needed.
Example
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
type User = { id: number; name: string; email: string };
@Component({
selector: 'app-user-detail',
template: `
<button (click)="userId.set(userId() + 1)">Next user</button>
@if (user.isLoading()) {
<p>Loading...</p>
} @else if (user.error()) {
<p>Failed to load. <button (click)="user.reload()">Retry</button></p>
} @else if (user.hasValue()) {
<h2>{{ user.value().name }}</h2>
<p>{{ user.value().email }}</p>
}
`,
})
export class UserDetail {
protected userId = signal(1);
// refetches whenever userId changes; stale requests are cancelled
protected user = httpResource<User>(
() => `https://jsonplaceholder.typicode.com/users/${this.userId()}`);
}Remember
httpResource(() => url)andresource({ params, loader })expose async data as signals- Use
value(),hasValue(),isLoading(),error(),status()andreload() - Changing params aborts the in-flight request; returning undefined keeps it idle
Watch out for
- Using a resource for POST/DELETE mutations; resources are for reading reactive data.