Angular resource() and httpResource(): Signal-Based Data Loading

Angular Tutorial: Beginner to Advanced · 13. Advanced signals and data loading · 4 min read · Updated

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 params or 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 be idle, loading, reloading, resolved, local or error.

Try it yourself

  1. Add a "Next post" button to the post viewer that increases postId.
  2. Make the country search wait 300 ms by debouncing the query signal with RxJS interop.
  3. Show the previous post while the next one is loading, using status() === 'reloading'.

Quick check

  1. What makes an httpResource send a new request?
  2. Which request type should you not use a resource for?
  3. How does a resource avoid old responses overwriting new ones?

Next, extend templates themselves with custom directives. Read more in the resource guide.

Frequently asked questions

Does httpResource replace HttpClient?

No. It uses HttpClient underneath, so interceptors still apply. It covers signal-driven reads, while HttpClient remains the tool for writes and complex streams.

Can I use resources with server-side rendering?

Resources can run on the server, but check the current SSR guide for how data is reused in the browser. Browser-only APIs such as fetch to private URLs or localStorage need care.