Every Angular developer has lived this moment.
You fetch data.
You handle loading.
You handle errors.
You add reload logic.
And suddenly your component is 100 lines long.
Not because the feature is complex, but because async state is.
Angular’s Resource API is a signal-first solution to a problem we’ve been solving manually for years.
It doesn’t replace RxJS. It doesn’t hide async.
It makes async state behave like a signal.
The pain of async state in Angular
Before signals, async code in Angular usually meant:
-
HttpClientreturning Observables - the
asyncpipe in templates - manual flags like
isLoading,hasError,isComplete -
Subjects to trigger reloads -
takeUntil(destroy$)to avoid memory leaks
Even with the async pipe, most components still had to juggle three states:
- Loading – we’re waiting for data
- Success – we have data
- Error - something went wrong
A very typical template looked like this:
<div *ngIf="data$ | async as data; else loading">
<ng-container *ngIf="!error; else errorBlock">
{{ data.name }}
</ng-container>
</div>
<ng-template #loading>Loading…</ng-template>
<ng-template #errorBlock>Error loading data</ng-template>
It works but it’s verbose, repetitive and fragile.
Every new async operation adds more flags and more mental overhead.
What if async behaved like a signal?
Signals changed how we write synchronous state in Angular:
- reactive by default
- automatically tracked
- declarative
- no subscriptions
At some point, the obvious question emerged:
What if async code behaved like
computed()?
The Resource API is Angular’s answer.
What is the Resource API?
The Resource API is a signal-first abstraction for async state.
You define:
- reactive parameters
-
a loader function that returns a
Promiseor anObservable
Angular gives you:
- a single reactive resource
- built-in loading and error tracking
- automatic cancellation
- automatic teardown
- dependency tracking on signals
No subscribe().
No async pipe.
No manual flags.
Your first resource
Here’s the simplest example using the official API shape:
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-user',
template: `...`
})
export class UserComponent {
userId = signal(1);
user = resource({
params: () => this.userId(),
loader: ({ params }) =>
fetch(`/api/users/${params}`).then(r => r.json())
});
}
user is now one reactive unit that represents:
- the current value
- whether it’s loading
- whether it failed
Using a resource in the template
Resources expose signal helpers instead of forcing you to manage flags manually.
@if (user.isLoading()) {
<p>Loading…</p>
}
@else if (user.error()) {
<p>{{ user.error()?.message }}</p>
}
@else {
<h2>{{ user.value().name }}</h2>
}
Important:
value() throws if the resource isn’t ready.
Always check isLoading() or error() first.
Reactive dependencies: the core idea
Resources automatically track signals used inside params().
userId = signal(1);
user = resource({
params: () => this.userId(),
loader: ({ params }) =>
this.http.get(`/api/users/${params}`)
});
When userId changes:
- the resource invalidates
- the loader re-runs
- the UI updates automatically
No switchMap.
No Subjects.
No wiring code.
This is the key mental shift:
The resource reacts to signal changes, not events.
Reloading data manually
Sometimes you want an explicit refresh.
<button (click)="user.reload()">Reload</button>
That’s it.
The resource automatically transitions through a loading state again.
The three flavors of Resource
Angular provides three helpers for different use cases.
resource() – the general-purpose tool
Use it when:
- you want full control
- you return a Promise or Observable
- the async logic isn’t HTTP-specific
todos = resource({
loader: () => this.http.get('/api/todos')
});
httpResource() – HTTP without boilerplate
httpResource() is a specialized helper built on top of resource():
- unwraps the HTTP response
- handles
HttpErrorResponse - integrates cleanly with
HttpClient
import { httpResource } from '@angular/common/http';
user = httpResource(() =>
this.http.get<User>('/api/user')
);
Same mental model, fewer lines.
rxResource() – real-time streams
For data that emits multiple values over time:
- WebSockets
- Server-Sent Events
- live prices
- sensor data
import { rxResource } from '@angular/core/rxjs-interop';
updates = rxResource(() =>
new EventSource('/api/events')
);
The resource value updates whenever the stream emits.
Connection and teardown are handled automatically.
Before vs After: a real comparison
Before (classic RxJS setup)
private destroy$ = new Subject<void>();
private reload$ = new Subject<void>();
category$ = new BehaviorSubject('all');
isLoading = false;
products$ = this.category$.pipe(
switchMap(cat => {
this.isLoading = true;
return this.http.get(`/api/products?cat=${cat}`).pipe(
finalize(() => this.isLoading = false)
);
}),
takeUntil(this.destroy$)
);
After (Resource API)
category = signal('all');
products = httpResource(() => {
const cat = this.category();
return this.http.get(`/api/products?cat=${cat}`);
});
One resource.
No flags.
No Subjects.
No teardown logic.
Common gotchas
Reading signals outside params()
const id = this.userId();
user = resource({
params: () => id, // not reactive
loader: ({ params }) => ...
});
Always read signals inside params().
Accessing value() too early
@if (user.isLoading()) {
{{ user.value() }} <!-- will throw -->
}
Always guard access to value().
Does this replace RxJS?
No, and it shouldn’t.
- RxJS is excellent for streams and orchestration
- Resource is excellent for async state
A good rule of thumb:
- RxJS for flows
- Resource for state
They work best together.
Where should a resource live?
- In a component Perfect for local, view-specific async state
- In a service Great for shared or global state. Resources are signals, so they’re injectable and composable.
Final thoughts
The Resource API doesn’t kill RxJS.
It removes the noise:
- fewer pipes
- fewer flags
- built-in loading and error handling
- signal-native async logic
Async state becomes predictable, reactive and boring and that’s a compliment.
If you’re already using signals, the Resource API is the missing piece.
Async, finally made human.
Top comments (0)