resource() / rxResource(): Signal-Based HTTP Loading
- Last updated
- Prerequisites:
- Angular signals and computed
- Understanding of HTTP requests and observables (for rxResource)
- angular
- resource
- rxResource
- async
- signals
- http
- loading
Read at your depth
The practical view
resource() is a signal-based wrapper for async loading. const user = resource({ request: () => this.userId(), loader: ({ request }) => fetch(`/users/${request}`).then(r => r.json()) });. Reading user.value() gives the data, user.isLoading(), user.error(), user.status(). When the request signal changes, the loader re-runs automatically. It also supports streaming (ArrayBuffer/ReadableStream) and state transitions. rxResource() is the RxJS-flavored version: the loader returns an Observable, which it subscribes to (with cancellation). These replace the manual pattern of writing an effect that sets a loading/data/error signal trio.
The same idea in other frameworks
React 19's use() with Suspense and TanStack Query's useQuery are the analogues.resource()/rxResource() — the current topic.VueUse's useAsyncState and Nuxt's useAsyncData mirror the request-tracking pattern.SvelteKit's load functions and $effect-based fetching are the Svelte-side analogue.Legacy vs modern
Manual effect + signals trio vs resource()
Hand-writing an effect that sets loading/data/error signals is boilerplate and race-prone; resource() handles lifecycle, races, and cancellation declaratively.
data = signal<User | null>(null);
loading = signal(false);
error = signal<Error | null>(null); // ❌ OLD: three signals + effectuser = resource({
request: () => this.userId(),
loader: ({ request }) => fetch(`/users/${request}`).then((r) => r.json()),
}); // ✅ NEWInterview gotchas
Context
This tests the race-condition handling — a core reason to use resource() — interviewers want the generation/abort mechanics, not just 'it handles it'.
The mechanical answer
Each loader invocation is associated with the request generation at the time it started. When the request computation changes, the resource bumps the generation and starts a new loader run; the previous invocation is marked stale, and its eventual resolution is ignored — it cannot write into the value slot. Depending on the loader, the old work may also be aborted (for fetch-based loaders, an AbortController is passed; for rxResource, the subscription is cancelled). This guarantees the value signal always reflects the latest request, eliminating the classic 'out-of-order response' bug that manual effect-based code suffers from. The framework also coalesces rapid request changes through the scheduler so you don't fire N parallel loaders for N quick input changes.
Trap
The naive answer is 'it just does the requests in order'. Ordering alone doesn't prevent slow-first/fast-second races; the generation guard is what discards stale results. Another trap: assuming the loader is called synchronously on every request change — it is scheduled, and intermediate request changes are coalesced. Candidates who mention generation tokens/abort and scheduler coalescing show real understanding.
Context
This probes the promise-based vs observable-based loader distinction and the integration points (HttpClient vs fetch) — a practical decision interviewers test.
The mechanical answer
resource() takes a loader that returns a Promise (or a streaming value like ArrayBuffer); it is the generic, framework-agnostic option — works with fetch, async/await, and any promise-based client. rxResource() takes a loader that returns an Observable, which the resource subscribes to; it is for code already in the RxJS world, especially when using HttpClient (which returns Observables) or when you need operators (debounce, switchMap, retry) inside the loader. Both provide the same value/loading/error/status surface and both cancel the previous request on change. The choice is essentially: promise/fetch codebase → resource(); RxJS/HttpClient codebase → rxResource(). You can also mix, since both integrate with signals.
Trap
The naive answer is 'rxResource is just the reactive version, always prefer it'. If you're not using RxJS, adding it just for rxResource is wrong — resource() is lighter and works with fetch. Another trap: assuming the loader runs inside an injection context automatically — it does for both (you can call inject() in the loader), but forgetting this when moving code from a service can cause NG0203 if called outside. Candidates who can match the primitive to the data layer show practical judgment.