S

super-dev — full-stack

@super-dev.app · Full-stack · ★ open to work
12.8k subscribers·47 videos·Active since 2017

Full-stack .NET / Angular dev with a DevOps streak on Azure Cloud. Also fluent in Flutter + Firebase. I build products, ship them, and keep them healthy.

Back to articles
ANGULAR
ANGULAR

Loading data with resource() and httpResource()

Article 2 of 4 — Angular 21 in practice
Zoneless, signals, resource(), @defer, SignalStore — the modern front end, for real.

Loading async data long meant manual subscribe() calls, hand-rolled state ( loading , error , data ) and memory leaks whenever you forgot an unsubscribe . Since Angular 21, resource() and httpResource() wrap all of that into a reactive primitive built on signals .

The resource() model

A resource() ties a reactive request to an async loader . When a signal read in params changes, Angular re-runs the loader automatically and cancels the in-flight request through an AbortSignal . The result is an object of signals: value() , error() , status() , plus isLoading() .

TypeScript
1import { resource, signal } from '@angular/core';
2
3export class UserCard {
4 private readonly userId = signal(1);
5
6 protected readonly user = resource({
7 params: () => ({ id: this.userId() }),
8 loader: ({ params, abortSignal }) =>
9 fetch(`/api/users/${params.id}`, { abortSignal }).then((response) =>
10 response.json(),
11 ),
12 });
13
14 protected next(): void {
15 this.userId.update((id) => id + 1);
16 }
17}

Changing userId is enough: no subscribe , no takeUntilDestroyed . The resource reloads, exposes isLoading() during the call, and aborts the previous request.

httpResource for REST calls

httpResource() is the variant tailored for HttpClient : it runs through interceptors, handles response typing and reacts to URL changes. You give it a function that returns the URL (or a full request object) derived from signals.

TypeScript
1import { httpResource } from '@angular/common/http';
2import { signal } from '@angular/core';
3
4export class ArticleList {
5 protected readonly tag = signal<string | undefined>(undefined);
6
7 protected readonly articles = httpResource<Article[]>(() => ({
8 url: '/api/articles',
9 params: this.tag() ? { tag: this.tag() } : {},
10 }));
11}

In the template, you consume the states directly, with no async pipe:

TypeScript
1@if (articles.isLoading()) {
2 <p>Loading…</p>
3} @else if (articles.error()) {
4 <p>Failed to load.</p>
5} @else {
6 @for (article of articles.value(); track article.id) {
7 <h3>{{ article.title }}</h3>
8 }
9}

States and their gotchas

status() returns one of idle , loading , reloading , resolved , error and local . Two subtleties deserve attention:

  • during a reload , value() keeps the previous data ( reloading ), which avoids a blank

screen — handy for a stale-while-revalidate pattern.

  • httpResource is meant for reads (GET). For a POST/PUT, stick with plain HttpClient :

a resource re-runs as soon as its request changes, which makes no sense for a mutation.

Why drop manual subscriptions

Imperative RxJS code mixes three concerns: triggering the call, mapping the stream, and cleaning up. With resource , the dependency becomes declarative — the loader re-runs because a signal changed, full stop. You delete the pagination BehaviorSubject s, the defensive switchMap s and the finalize calls that reset loading to false . The official docs cover the API in the async resource guide .

resource() doesn't replace RxJS: it replaces the plumbing . You describe what to load and what it depends on; Angular handles the when, the cancellation and the state. The component goes back to being a plain read of signals.
super-dev — portfolio.app
// More in ANGULAR
A DOOM-style engine in the browser, with three backends
11 min • 1.4k reads
Take an Angular app zoneless + signals
8 min • 4.1k reads
@defer and the new control flow: less JS at startup
6 min • 2.9k reads