Async data with resource and httpResource

Angular · Practical · 13 min

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 (undefined until loaded); hasValue() narrows it safely.
  • isLoading(), error(), and status() ('idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local').
  • reload() to fetch again, and set/update to 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) and resource({ params, loader }) expose async data as signals
  • Use value(), hasValue(), isLoading(), error(), status() and reload()
  • 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.

Check yourself

  1. The URL function of an httpResource returns undefined because no user is selected yet. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: angular.dev/guide/signals/resource