Skip to content
Angular ng signals 4 min read

Resource API

Most components don’t just hold state — they fetch it. A user picks an id, you load a record; a search box changes, you query an API. Wiring that up traditionally means juggling subscriptions, loading flags, error variables, and cancellation of stale requests. The resource() and rxResource() APIs (experimental, introduced in Angular 19) fold all of that into a single reactive primitive: you declare what the request depends on and how to load it, and Angular gives you a signal-based bundle of value, status, and error that re-fetches automatically when its inputs change.

What a resource gives you

A resource ties an asynchronous loader to a set of reactive request parameters. Whenever a signal read inside the request changes, the previous in-flight load is cancelled and a new one starts. The result is exposed entirely through signals, so it composes cleanly with computed(), effect(), and your template’s @if/@for control flow.

MemberTypeDescription
value()Signal<T | undefined>The loaded data, or undefined before the first success
status()Signal<ResourceStatus>'idle', 'loading', 'reloading', 'resolved', or 'error'
error()Signal<unknown>The thrown error, when status is 'error'
isLoading()Signal<boolean>True during the initial load and reloads
reload()() => booleanManually re-runs the loader with the current request

Using resource()

The resource() function takes a request function (the reactive inputs) and a loader (an async function that returns a Promise). The loader receives the resolved request value plus an abortSignal you can pass to fetch for automatic cancellation.

import { Component, signal, resource } from '@angular/core';

interface User {
  id: number;
  name: string;
  email: string;
}

@Component({
  selector: 'app-user-card',
  standalone: true,
  template: `
    <input type="number" [value]="userId()" (input)="setId($event)" />

    @if (userResource.isLoading()) {
      <p>Loading…</p>
    } @else if (userResource.error()) {
      <p class="error">Failed to load user.</p>
    } @else if (userResource.value(); as user) {
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
    }
  `,
})
export class UserCardComponent {
  readonly userId = signal(1);

  readonly userResource = resource({
    // Reactive inputs — changing userId() triggers a reload.
    request: () => ({ id: this.userId() }),
    // Loader runs whenever request changes; old requests are aborted.
    loader: async ({ request, abortSignal }) => {
      const res = await fetch(`/api/users/${request.id}`, { signal: abortSignal });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return (await res.json()) as User;
    },
  });

  setId(event: Event) {
    this.userId.set(Number((event.target as HTMLInputElement).value));
  }
}

When the user types a new id, request() produces a fresh object, Angular aborts the previous fetch, and the loader re-runs. The template reacts to isLoading(), error(), and value() without a single manual subscription.

Output:

status() -> "loading"            // initial fetch
status() -> "resolved"           // value() now holds the User
status() -> "reloading"          // user changed the id; old fetch aborted
status() -> "resolved"           // new User loaded

Tip: Returning undefined from the request function tells the resource the inputs aren’t ready yet — the loader is skipped and status() stays 'idle'. This is the clean way to defer loading until, say, an id actually exists.

Using rxResource()

If your data layer already returns Observables — for example HttpClient — use rxResource() from @angular/core/rxjs-interop. It’s identical in shape but the loader returns an Observable instead of a Promise. The resource takes the first emitted value and unsubscribes automatically; changing the request cancels the in-flight stream.

import { Component, signal, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-product',
  standalone: true,
  template: `
    @if (product.value(); as p) {
      <h2>{{ p.name }} — {{ p.price | currency }}</h2>
    }
  `,
})
export class ProductComponent {
  private http = inject(HttpClient);
  readonly productId = signal(42);

  readonly product = rxResource({
    request: () => this.productId(),
    loader: ({ request }) =>
      this.http.get<{ name: string; price: number }>(`/api/products/${request}`),
  });
}

resource() vs rxResource()

Aspectresource()rxResource()
Loader returnsPromise<T>Observable<T>
CancellationVia abortSignalAuto-unsubscribe on new request
Import@angular/core@angular/core/rxjs-interop
Best forfetch, async/await codeHttpClient, RxJS pipelines
Emissions usedThe resolved promiseThe first emission only

Updating and reloading

The value signal is read-only by default, but you can optimistically mutate it via the update/set helpers when the resource is defined as writable, and you can force a refresh with reload():

refresh() {
  // Re-runs the loader with the current request; returns false if already loading.
  this.userResource.reload();
}

Warning: These APIs are experimental. Names like ResourceStatus and the exact loader signature may shift between minor releases — pin your Angular version and check the changelog before upgrading in production.

Best practices

  • Keep the request function tiny and pure: read only the signals the load truly depends on, so you don’t trigger spurious re-fetches.
  • Always thread abortSignal into fetch so stale, superseded requests are cancelled instead of racing.
  • Return undefined from request to defer loading until inputs are valid, rather than guarding inside the loader.
  • Prefer rxResource() when you’re already using HttpClient — you get cancellation and interceptors for free.
  • Drive your template from status()/isLoading()/error() with @if/@else if; avoid maintaining parallel boolean flags.
  • Treat value() as possibly undefined on first render and narrow it with @if (res.value(); as v).
  • Because it’s experimental, isolate resource usage behind a small service so a future API change touches one file.
Last updated June 14, 2026
Was this helpful?