Skip to main content

6 min read #angular #signals #httpclient #rxjs #frontend

httpResource deleted half my Angular data-fetching code

resource(), rxResource(), and httpResource() turn async data into signals — value, loading, and error states for free. No subscribe, no unsubscribe, no manual loading booleans. I'm not writing that boilerplate again.

Here's a block of code I have written, no exaggeration, a hundred times across my Angular career:

users: User[] = [];
loading = false;
error: string | null = null;

ngOnInit() {
  this.loading = true;
  this.http.get<User[]>('/api/users').subscribe({
    next: (users) => { this.users = users; this.loading = false; },
    error: (err) => { this.error = err.message; this.loading = false; },
  });
}

A loading boolean, an error field, a subscription I have to remember to clean up, and three places where I manually flip loading on and off. Multiply by every list, detail page, and dropdown in the app. Angular's Resource APIs — resource(), rxResource(), and httpResource() — delete all of it. No cap, the first time I converted a component I removed more lines than I added and it did more.

httpResource: the whole pattern, as one signal

httpResource() wraps HttpClient and hands back a resource whose value, loading flag, and error are all signals:

usersResource = httpResource<User[]>(() => '/api/users');

// in the component, everything is a signal:
this.usersResource.value();     // User[] | undefined
this.usersResource.isLoading(); // boolean
this.usersResource.error();     // unknown | undefined
this.usersResource.status();    // 'idle' | 'loading' | 'resolved' | 'error' ...

The template stops being a subscription-management exercise and becomes a straight read of state:

@if (usersResource.isLoading()) {
  <app-spinner />
} @else if (usersResource.error()) {
  <app-error [message]="usersResource.error()" />
} @else {
  @for (user of usersResource.value(); track user.id) {
    <app-user-card [user]="user" />
  }
}

No subscribe. No takeUntilDestroyed. No loading boolean I forgot to reset on the error path. The resource owns its lifecycle and tears down with the component.

The good part: it refetches when its inputs change

This is where it stops being "nice syntax" and becomes genuinely better than what I was writing. The request is a function, so if it reads a signal, the resource re-runs whenever that signal changes:

selectedId = signal(1);

userResource = httpResource<User>(
  () => `/api/users/${this.selectedId()}`
);

// later — this single line triggers a refetch:
this.selectedId.set(42);

Change the id, the URL recomputes, the resource refetches, the loading signal flips, the view reacts. I used to wire this up with a Subject, a switchMap, and a comment apologizing for the race condition. Now it's a dependency the framework tracks for me. And switchMap semantics — cancel the in-flight request when the input changes — come for free.

Three resources, pick your altitude

  • httpResource() — the high-level one, for "fetch this URL." Gives you status() and headers on errors too, so you can branch on a 404 vs a 500.
  • resource() — the generic version that takes any async loader returning a Promise. Use it for anything that isn't a plain HTTP GET — IndexedDB, the File System Access API, a third-party SDK.
  • rxResource() — the escape hatch when you genuinely need RxJS operators (debounced search, retry/backoff, combining streams). It takes an Observable-returning loader and still surfaces everything as signals.

That last one matters: this isn't "Angular abandoning RxJS." It's RxJS where it earns its complexity, and signals everywhere else. You get reload(), set(), and update() on the resource for the cases where you want to imperatively poke it (optimistic updates, manual refresh button).

The honest caveat

httpResource is still experimental and the API moved a bit between v19 and v21 — error status exposure, in particular, got fixed along the way. One sharp edge: reading value() while the resource is in an error state throws, so branch on error()/isLoading() first (the template pattern above already does). It's labeled experimental, but it's the kind of experimental I happily ship in side projects today.

The boring conclusion

Data fetching was the last big chunk of Angular where you still wrote imperative, subscription-juggling, loading-boolean code in 2025. The Resource APIs turn it into declarative signal state that refetches when its inputs change and cleans up after itself. It's less code, fewer bugs, and no more "did I unsubscribe?" anxiety. I converted one component to try it and ended up converting the whole app, because going back to .subscribe() felt like going back to jQuery.