Aller au contenu principal

rxResource (Angular 21)

What is rxResource?

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

Why Use It?

  • Automatique loading states - No manual isLoading flags
  • Error handling - Built-in error state
  • Reactive loading - Auau-reloads when dependencies change
  • Cleaner code - Moins de boilerplate than manual HttpClient
  • Basé sur signal - Works seamlessly with signals

rxResource vs HttpClient

FeatureHttpClientrxResource
Loading stateManualAutomatique
Error stateManual try/catchAutomatique
Reactive reloadManual effect/subscribeAutomatique
NettoyageManual unsubscribeAutomatique
Result typeObservableSignal

Basic Utilisation

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')
});

// Accéder à 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 automatiqueally:

  • value() - The loaded data
  • isLoading() - Loading state
  • error() - Error state
  • status() - Overall status
  • reload() - Method à reload manually

Reactive Loading with Request

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}`)
});
// Automatiqueally reloads when userId() changements !

loadUser() {
this.userId.update(id => id + 1);
// user resource automatiqueally reloads!
}
}

How it works:

  1. request returns an object with dependencies
  2. When any dependency changements, loader runs again
  3. Loading states update automatiqueally
  4. Ancien requests sont cancelled automatiqueally

Loading Status

@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 à load
// - 'success' - Successfully loaded
}

Manual Reload

@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() à manually trigger a reload
// Utile for refresh boutons, polling, etc.
}

Error Handling

@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';
}
}

Real-World Exemple: Pagination

@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;

// Automatiqueally reloads when currentPage changements
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;
}

Bonnes Pratiques

  • ✅ Utiliser pour loading data from APIs - Simplifies HTTP calls
  • ✅ Perfect for lists, details, searches - Reactive data loading
  • ✅ Great for pagination, filtering - Auau-reload on param changements
  • ✅ Utiliser request for reactive dependencies - Auau-reload when they change
  • ✅ Handle errors in template - Show user-friendly messages
  • ✅ Utiliser reload() for manual refresh - Refresh boutons, polling
  • ❌ Don't use for POST/PUT/DELETE - Utiliser regular HttpClient
  • ❌ Not for complex state - Consider a state management library
  • ❌ Avoid nested resources - Can cause performance issues

rxResource vs Alternatives

ApproachUtiliser Case
rxResourceLoading data with automatique states
HttpClientOne-off requests, mutations (POST/PUT/DELETE)
Service + BehaviorSubjectComplex shsontd state across components
State management (NgRx)Large-scale application state

Common Patterns

PatternUtiliser Case
Simple listLoad users, products, etc.
Detail viewLoad single item by ID
SearchReactive search with auau-reload
PaginationPage through results
FilteringFilter lists by criteria
PollingAuau-refresh data periodically
Dependent requestsLoad data based on another signal