Skip to content

rxResource (Angular 21)

rxResource is a new Angular 21 primitive that simplifies loading data from APIs. It automatically handles loading states, errors, and reactive updates when dependencies change.

  • ✅ Automatic loading states - No manual isLoading flags
  • ✅ Error handling - Built-in error state
  • ✅ Reactive loading - Auto-reloads when dependencies change
  • ✅ Cleaner code - Less boilerplate than manual HttpClient
  • ✅ Signal-based - Works seamlessly with signals
Feature HttpClient rxResource
Loading state Manual Automatic
Error state Manual try/catch Automatic
Reactive reload Manual effect/subscribe Automatic
Cleanup Manual unsubscribe Automatic
Result type Observable Signal
import { Component } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-list',
standalone: true,
template: `
<div>
<!-- Loading state -->
<div *ngIf="users.isLoading()">Loading users...</div>
<!-- Error state -->
<div *ngIf="users.error()" class="error">
Error: {{ users.error()?.message }}
</div>
<!-- Success state -->
<div *ngIf="users.value()">
<div *ngFor="let user of users.value()">
{{ user.name }} - {{ user.email }}
</div>
</div>
<!-- Status -->
<p>Status: {{ users.status() }}</p>
</div>
`
})
export class UserListComponent {
http = inject(HttpClient);
// Create a resource that loads users
users = rxResource({
loader: () => this.http.get<User[]>('/api/users')
});
// Access the data:
// users.value() - The loaded data (or undefined)
// users.isLoading() - Boolean loading state
// users.error() - Error object (or null)
// users.status() - 'idle' | 'loading' | 'error' | 'success'
}

What you get automatically:

  • ✅ value() - The loaded data
  • ✅ isLoading() - Loading state
  • ✅ error() - Error state
  • ✅ status() - Overall status
  • ✅ reload() - Method to reload manually
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-user-detail',
standalone: true,
template: `
<div>
<input
type="number"
[(ngModel)]="userId"
placeholder="Enter user ID" />
<button (click)="loadUser()">Load User</button>
<div *ngIf="user.isLoading()">Loading user...</div>
<div *ngIf="user.error()">Error: {{ user.error()?.message }}</div>
<div *ngIf="user.value()">
<h2>{{ user.value()!.name }}</h2>
<p>{{ user.value()!.email }}</p>
</div>
</div>
`
})
export class UserDetailComponent {
http = inject(HttpClient);
userId = signal(1);
// Resource with reactive request
user = rxResource({
request: () => ({ id: this.userId() }),
loader: ({ request }) => this.http.get<User>(`/api/users/${request.id}`)
});
// Automatically reloads when userId() changes!
loadUser() {
this.userId.update(id => id + 1);
// user resource automatically reloads!
}
}

How it works:

  1. request returns an object with dependencies
  2. When any dependency changes, loader runs again
  3. Loading states update automatically
  4. Old requests are cancelled automatically
@Component({
selector: 'app-data-view',
standalone: true,
template: `
<div [ngSwitch]="data.status()">
<div *ngSwitchCase="'idle'">
<button (click)="data.reload()">Load Data</button>
</div>
<div *ngSwitchCase="'loading'">
<spinner></spinner> Loading...
</div>
<div *ngSwitchCase="'error'">
<p class="error">{{ data.error()?.message }}</p>
<button (click)="data.reload()">Retry</button>
</div>
<div *ngSwitchCase="'success'">
<pre>{{ data.value() | json }}</pre>
<button (click)="data.reload()">Refresh</button>
</div>
</div>
`
})
export class DataViewComponent {
http = inject(HttpClient);
data = rxResource({
loader: () => this.http.get('/api/data')
});
// data.status() can be:
// - 'idle' - Not loaded yet
// - 'loading' - Currently loading
// - 'error' - Failed to load
// - 'success' - Successfully loaded
}
@Component({
selector: 'app-messages',
standalone: true,
template: `
<div>
<button (click)="messages.reload()">Refresh Messages</button>
<div *ngFor="let message of messages.value()">
{{ message.text }}
</div>
<p>Last updated: {{ lastUpdate }}</p>
</div>
`
})
export class MessagesComponent {
http = inject(HttpClient);
lastUpdate = signal(new Date());
messages = rxResource({
loader: () => {
this.lastUpdate.set(new Date());
return this.http.get<Message[]>('/api/messages');
}
});
// Call reload() to manually trigger a reload
// Useful for refresh buttons, polling, etc.
}
@Component({
selector: 'app-error-handling',
standalone: true,
template: `
<div>
<!-- Show error message -->
<div *ngIf="data.error()" class="alert-error">
<h3>Failed to load data</h3>
<p>{{ data.error()!.message }}</p>
<button (click)="data.reload()">Try Again</button>
</div>
<!-- Show success -->
<div *ngIf="data.value()">
<pre>{{ data.value() | json }}</pre>
</div>
<!-- Error status -->
<div *ngIf="data.status() === 'error'">
<p>Status Code: {{ getStatusCode() }}</p>
</div>
</div>
`
})
export class ErrorHandlingComponent {
http = inject(HttpClient);
data = rxResource({
loader: () => this.http.get('/api/data')
});
getStatusCode() {
const error = this.data.error() as any;
return error?.status ?? 'Unknown';
}
}
@Component({
selector: 'app-user-list-paginated',
standalone: true,
template: `
<div>
<h2>Users (Page {{ currentPage() }})</h2>
<div *ngIf="users.isLoading()">Loading page...</div>
<div *ngIf="users.value()">
<div *ngFor="let user of users.value()!.data">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
<div class="pagination">
<button
(click)="prevPage()"
[disabled]="currentPage() === 1 || users.isLoading()">
Previous
</button>
<span>Page {{ currentPage() }} of {{ users.value()?.totalPages }}</span>
<button
(click)="nextPage()"
[disabled]="currentPage() === users.value()?.totalPages || users.isLoading()">
Next
</button>
</div>
</div>
</div>
`
})
export class UserListPaginatedComponent {
http = inject(HttpClient);
currentPage = signal(1);
pageSize = 10;
// Automatically reloads when currentPage changes
users = rxResource({
request: () => ({ page: this.currentPage() }),
loader: ({ request }) =>
this.http.get<PaginatedResponse<User>>('/api/users', {
params: {
page: request.page.toString(),
pageSize: this.pageSize.toString()
}
})
});
nextPage() {
this.currentPage.update(p => p + 1);
}
prevPage() {
this.currentPage.update(p => Math.max(1, p - 1));
}
}
interface PaginatedResponse<T> {
data: T[];
page: number;
totalPages: number;
total: number;
}
  • ✅ Use for loading data from APIs - Simplifies HTTP calls
  • ✅ Perfect for lists, details, searches - Reactive data loading
  • ✅ Great for pagination, filtering - Auto-reload on param changes
  • ✅ Use request for reactive dependencies - Auto-reload when they change
  • ✅ Handle errors in template - Show user-friendly messages
  • ✅ Use reload() for manual refresh - Refresh buttons, polling
  • ❌ Don’t use for POST/PUT/DELETE - Use regular HttpClient
  • ❌ Not for complex state - Consider a state management library
  • ❌ Avoid nested resources - Can cause performance issues
Approach Use Case
rxResource Loading data with automatic states
HttpClient One-off requests, mutations (POST/PUT/DELETE)
Service + BehaviorSubject Complex shared state across components
State management (NgRx) Large-scale application state
Pattern Use Case
Simple list Load users, products, etc.
Detail view Load single item by ID
Search Reactive search with auto-reload
Pagination Page through results
Filtering Filter lists by criteria
Polling Auto-refresh data periodically
Dependent requests Load data based on another signal