Resource API et httpResource Angular : guide complet (stable depuis Angular 22)
La Resource API et httpResource sont stables depuis Angular 22. Ce guide couvre resource(), rxResource(), httpResource(), le nouveau helper chain() pour les resources dépendantes, la validation Zod et les questions d'entretien.

Angular propose la Resource API et httpResource pour la récupération de données fondée sur les signals, avec un suivi automatique des états de chargement, d'erreur et de résolution. Introduites dans Angular 20, ces API sont stables depuis Angular 22, remplaçant les schémas de souscription verbeux de HttpClient par des primitives réactives.
La Resource API (resource(), rxResource(), httpResource()) est passée d'expérimentale à stable dans Angular 22 (juin 2026). Le nommage params et stream, les statuts en littéraux de chaîne, et le nouveau helper chain() pour les resources dépendantes sont prêts pour la production. Pour une mise à niveau depuis Angular 20 ou 21, la surface de l'API reste compatible.
Les trois variantes de Resource
Angular propose trois manières de charger des données asynchrones sous forme de signals. Chacune cible un cas d'usage différent, mais toutes partagent le même modèle réactif : déclarer les dépendances, définir un loader, puis consommer le résultat via des signals.
resource()fonctionne avec les Promises. Idéal pourfetch()ou toute API fondée sur les promesses.rxResource()fonctionne avec les Observables. Le bon choix lorsque des opérateurs RxJS commedebounceTime,retryouswitchMapsont nécessaires.httpResource()encapsule directement leHttpClientd'Angular. Les interceptors, les utilitaires de test et la validation de schéma fonctionnent sans configuration supplémentaire.
La différence essentielle entre httpResource et les deux autres : httpResource s'appuie sur HttpClient en interne, ce qui signifie que les interceptors existants continuent de fonctionner. L'API resource() d'origine contournait totalement HttpClient, un point de friction majeur dans Angular 19.
Construire un profil utilisateur avec resource()
La fonction resource() accepte une fonction de calcul params et une fonction loader. Lorsque les signals présents dans params changent, le loader se réexécute automatiquement.
import { Component, signal, resource } from '@angular/core';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: \`
@if (userResource.hasValue()) {
<h2>{{ userResource.value().name }}</h2>
<p>{{ userResource.value().email }}</p>
} @else if (userResource.isLoading()) {
<p>Loading profile...</p>
} @else if (userResource.error()) {
<p>Failed to load user</p>
}
\`,
})
export class UserProfileComponent {
userId = signal(1);
// params produces the reactive dependency
// loader receives it and returns a Promise
userResource = resource<User, number>({
params: () => this.userId(),
loader: async ({ params: id, abortSignal }) => {
const res = await fetch(\`/api/users/\${id}\`, { signal: abortSignal });
return res.json();
},
});
loadUser(id: number) {
this.userId.set(id); // triggers automatic refetch
}
}Le paramètre abortSignal permet à Angular d'annuler les requêtes en cours lorsque userId change avant que la requête précédente ne se termine. Cela évite les conditions de course sans gestion manuelle des souscriptions.
Récupération réactive de données avec httpResource
httpResource élimine le code répétitif en combinant la déclaration de l'URL et l'exécution HTTP en un seul appel. Il retourne un HttpResourceRef qui expose value, isLoading, error, status et headers sous forme de signals.
import { Component, signal, computed } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface Product {
id: number;
name: string;
price: number;
category: string;
}
@Component({
selector: 'app-product-list',
template: \`
@if (products.hasValue()) {
@for (product of products.value(); track product.id) {
<div class="product-card">
<h3>{{ product.name }}</h3>
<span>{{ product.price | currency }}</span>
</div>
}
} @else if (products.isLoading()) {
<p>Loading products...</p>
}
\`,
})
export class ProductListComponent {
category = signal('electronics');
// httpResource re-fetches whenever category() changes
products = httpResource<Product[]>(() => ({
url: '/api/products',
params: { category: this.category() },
}));
filterByCategory(cat: string) {
this.category.set(cat); // pending request is cancelled, new one starts
}
}La fonction passée à httpResource retourne un objet de configuration de requête. Angular suit les lectures de signals à l'intérieur de cette fonction : modifier category déclenche donc une nouvelle requête GET. Si une requête est déjà en cours, Angular l'annule avant de lancer la nouvelle.
httpResource est conçu pour la récupération de données (requêtes GET). L'utiliser pour des opérations POST, PUT ou DELETE est dangereux, car l'annulation d'une requête pourrait interrompre une mutation en cours. Pour les opérations d'écriture, utiliser directement HttpClient ou encapsuler les mutations dans une méthode de service.
Chaîner des resources avec chain()
Angular 22 introduit le helper chain() pour le chargement de resources dépendantes. Lorsqu'une resource dépend de la valeur d'une autre, chain() propage automatiquement les états de chargement et d'erreur.
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface User {
id: number;
name: string;
authorId: string;
}
interface Post {
id: number;
title: string;
content: string;
}
@Component({
selector: 'app-user-posts',
template: \`
@if (postsResource.isLoading()) {
<p>Loading...</p>
} @else if (postsResource.hasValue()) {
<h2>Posts by {{ userResource.value().name }}</h2>
@for (post of postsResource.value(); track post.id) {
<article>
<h3>{{ post.title }}</h3>
</article>
}
} @else if (postsResource.error()) {
<p>Failed to load posts</p>
}
\`,
})
export class UserPostsComponent {
userId = signal(1);
userResource = httpResource<User>(() => \`/api/users/\${this.userId()}\`);
// chain() propagates userResource's status to postsResource
postsResource = httpResource<Post[]>({
params: ({ chain }) => {
const user = chain(this.userResource);
return { url: '/api/posts', params: { authorId: user.authorId } };
},
});
}chain(userResource) ne se contente pas de retourner la valeur. Pendant que userResource est en cours de chargement, postsResource indique également un chargement et son loader ne s'exécute pas. Si userResource passe en erreur, postsResource passe aussi en état d'erreur avec une ResourceDependencyError qui contient l'erreur en amont.
Ce mécanisme remplace le schéma RxJS des appels switchMap imbriqués par une propagation explicite et correcte par défaut des statuts.
Validation de schéma avec Zod et httpResource
Les réponses des services externes peuvent dévier de la forme attendue. L'option parse de httpResource intègre des bibliothèques de validation de schéma comme Zod afin de détecter les écarts à l'exécution, au lieu de propager silencieusement des données corrompues.
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
import { z } from 'zod';
// Define the expected shape with Zod
const OrderSchema = z.object({
id: z.number(),
status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
total: z.number().positive(),
items: z.array(z.object({
productId: z.number(),
quantity: z.number().int().positive(),
unitPrice: z.number().positive(),
})),
createdAt: z.string().datetime(),
});
type Order = z.infer<typeof OrderSchema>;
@Component({
selector: 'app-order',
template: \`
@if (order.hasValue()) {
<h2>Order #{{ order.value().id }}</h2>
<p>Status: {{ order.value().status }}</p>
<p>Total: {{ order.value().total | currency }}</p>
} @else if (order.error()) {
<p>Invalid order data received</p>
}
\`,
})
export class OrderComponent {
orderId = signal(42);
// parse validates the response before exposing it as a signal
order = httpResource<Order>(
() => \`/api/orders/\${this.orderId()}\`,
{ parse: OrderSchema.parse }
);
}Lorsque l'API retourne des données qui ne correspondent pas à OrderSchema, la resource passe au statut 'error'. Le type de retour de la fonction parse détermine également le type TypeScript de value() : les définitions de schéma jouent donc un double rôle, validateurs à l'exécution et générateurs de types.
rxResource avec stream et params
Angular renomme loader en stream et request en params dans rxResource. Ce changement aligne la nomenclature sur la sémantique de flux que rxResource prend en charge. La fonction stream reçoit un contexte d'Observable et doit retourner un Observable.
import { Component, signal } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { inject } from '@angular/core';
import { debounceTime, switchMap } from 'rxjs';
interface SearchResult {
id: number;
title: string;
excerpt: string;
}
@Component({
selector: 'app-search',
template: \`
<input (input)="query.set((.target).value)" placeholder="Search..." />
@if (results.isLoading()) {
<p>Searching...</p>
}
@if (results.hasValue()) {
@for (item of results.value(); track item.id) {
<div>{{ item.title }}</div>
}
}
\`,
})
export class SearchComponent {
private http = inject(HttpClient);
query = signal('');
// params (was "request") provides the reactive input
// stream (was "loader") returns an Observable
results = rxResource<SearchResult[], string>({
params: () => this.query(),
stream: ({ params: q }) =>
this.http.get<SearchResult[]>('/api/search', {
params: { q },
}),
});
}Contrairement à httpResource, rxResource offre un contrôle total sur le pipeline d'Observable. Des opérateurs comme debounceTime ou retry peuvent être chaînés à l'intérieur de stream. En revanche, httpResource traite le cas le plus courant (une seule requête GET) avec moins de code.
Prêt à réussir tes entretiens Angular ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Suivi du statut : les littéraux de chaîne remplacent les énumérations
ResourceStatus est un type union de chaînes. Les six valeurs possibles offrent une vision fine du cycle de vie de la resource :
| Statut | Signification |
|---|---|
'idle' | params a retourné undefined, aucune requête émise |
'loading' | Première requête en cours |
'reloading' | Requête suivante après un succès précédent |
'resolved' | Données disponibles dans value() |
'error' | Échec de la requête, error() contient l'erreur |
'local' | Valeur définie localement via .set() ou .update() |
import { Component, signal, resource } from '@angular/core';
@Component({
selector: 'app-status-demo',
template: \`
<p>Status: {{ data.status() }}</p>
@switch (data.status()) {
@case ('loading') { <spinner /> }
@case ('reloading') { <subtle-spinner /> }
@case ('resolved') { <data-table [rows]="data.value()" /> }
@case ('error') { <error-banner [error]="data.error()" /> }
@case ('idle') { <p>Select a filter to load data</p> }
}
\`,
})
export class StatusDemoComponent {
filter = signal<string | undefined>(undefined);
data = resource({
params: () => this.filter(),
loader: async ({ params: f, abortSignal }) => {
const res = await fetch(\`/api/data?filter=\${f}\`, { signal: abortSignal });
return res.json();
},
});
}Retourner undefined depuis params fixe le statut à 'idle' et empêche le loader de s'exécuter. Ce schéma convient bien à la récupération conditionnelle de données : afficher une invite tant que l'utilisateur n'a pas fourni d'entrée, puis charger les données.
Appeler value() sur une resource au statut 'error' lève une exception à l'exécution. Toujours protéger les lectures avec hasValue() ou vérifier status() avant d'accéder à value(). Il s'agit d'un changement par rapport à Angular 19, où value() retournait undefined en cas d'erreur.
Questions d'entretien Angular sur httpResource
La Resource API est un sujet courant des questions d'entretien Angular. Voici des questions qui testent une compréhension réelle, au-delà d'une familiarité superficielle avec l'API.
Q : Quel problème httpResource résout-il que resource() ne résout pas ?
resource() utilise fetch() ou tout loader fondé sur une promesse, en contournant le HttpClient d'Angular. Les interceptors (jetons d'authentification, journalisation, gestion d'erreurs) ne s'appliquent donc pas. httpResource utilise HttpClient en interne : les interceptors, les utilitaires de test (HttpTestingController) et des fonctionnalités comme withFetch() fonctionnent sans configuration supplémentaire.
Q : Quand faut-il préférer rxResource à httpResource ?
rxResource offre un contrôle total sur l'Observable via sa fonction stream. Le choisir lorsque le pipeline de données nécessite des opérateurs RxJS : limiter une saisie de recherche, réessayer des requêtes échouées avec un backoff exponentiel, ou combiner plusieurs flux avec combineLatest. Pour de simples requêtes GET, httpResource demande moins de code.
Q : Comment Angular gère-t-il les requêtes concurrentes lorsqu'un signal change rapidement ?
Les trois variantes de resource annulent les requêtes en cours lorsque params produit une nouvelle valeur. Pour httpResource et resource(), l'AbortSignal annule le fetch sous-jacent. Pour rxResource, Angular se désabonne de l'Observable précédent. Cela empêche des réponses obsolètes d'écraser des données fraîches.
Q : À quoi sert le statut 'local' ?
Appeler .set() ou .update() sur une resource modifie sa valeur localement sans déclencher le loader. Le statut passe à 'local', ce qui indique que la valeur courante ne provient pas du serveur. Cela facilite les mises à jour optimistes de l'interface : l'UI reflète le changement immédiatement pendant qu'une requête de mutation distincte s'exécute.
Q : Comment fonctionne l'intégration de Zod avec httpResource ?
L'option parse accepte toute fonction de signature (data: unknown) => T. À l'arrivée de la réponse HTTP, httpResource passe le JSON analysé à travers parse avant de définir value(). Si parse lève une exception (par exemple ZodError), la resource passe au statut 'error'. Le type de retour de parse détermine le type TypeScript de value(), offrant à la fois sécurité à l'exécution et types à la compilation à partir d'une seule définition de schéma.
Q : Comment chain() gère-t-il les erreurs des resources en amont ?
Lorsqu'on utilise chain(upstreamResource) dans la fonction params d'une resource, la resource dépendante n'exécute pas son loader si la resource en amont est en cours de chargement, inactive ou en erreur. Si la resource en amont passe en erreur, la resource dépendante passe aussi en état d'erreur avec une ResourceDependencyError. Cette erreur inclut la dependency (la resource en amont) et la cause (l'erreur réelle en amont), permettant de distinguer « ma requête a échoué » de « la requête de ma dépendance a échoué ».
Pour approfondir les signals Angular et leur intégration dans le framework au sens large, le guide sur les signals couvre les computed signals, les effects et le modèle de réactivité.
Migrer des souscriptions HttpClient vers httpResource
Les applications Angular existantes récupèrent généralement des données via des souscriptions HttpClient dans ngOnInit, ou utilisent l'AsyncPipe avec des Observables. La migration vers httpResource suit un schéma prévisible :
// BEFORE: manual subscription in ngOnInit
@Component({ /* ... */ })
export class BeforeComponent implements OnInit, OnDestroy {
private http = inject(HttpClient);
private destroy$ = new Subject<void>();
users: User[] = [];
loading = false;
error: string | null = null;
ngOnInit() {
this.loading = true;
this.http.get<User[]>('/api/users')
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (data) => { this.users = data; this.loading = false; },
error: (err) => { this.error = err.message; this.loading = false; },
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
// AFTER: httpResource handles lifecycle automatically
@Component({ /* ... */ })
export class AfterComponent {
users = httpResource<User[]>(() => '/api/users');
// No ngOnInit, no Subject, no manual unsubscribe
// Template uses users.value(), users.isLoading(), users.error()
}La migration supprime le code répétitif de gestion du cycle de vie. Le nettoyage des souscriptions se fait automatiquement à la destruction du composant. Les états de chargement et d'erreur sont intégrés à la resource : plus besoin de drapeaux booléens séparés.
Pour les applications utilisant déjà les composants standalone, la migration est directe : remplacer l'injection de HttpClient et la logique de souscription par une unique déclaration httpResource.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Sources
- Documentation de la Resource API Angular : guide officiel sur resource(), rxResource() et httpResource()
- What's new in Angular 22.0 : couverture par Ninja Squad de la stabilisation de la Resource API et du helper chain()
- Guide des interceptors HTTP Angular : fonctionnement des interceptors avec httpResource
Ce qu'il faut retenir de la Resource API Angular
httpResourceremplace les souscriptions manuelles àHttpClientpar une seule déclaration réactive qui gère automatiquement le chargement, l'erreur et l'annulation- Utiliser
resource()pour les API fondées sur les promesses,rxResource()pour les pipelines d'Observables avec opérateurs RxJS, ethttpResource()pour les appels HTTP standard avec prise en charge des interceptors - Le helper
chain()(Angular 22+) permet le chargement de resources dépendantes avec propagation automatique des statuts ; si la resource en amont échoue, la dépendante reçoit uneResourceDependencyError - Les valeurs de statut sont des littéraux de chaîne (
'idle','loading','resolved','error','reloading','local') - L'option
parsedehttpResourceintègre Zod ou Valibot pour une validation de schéma à l'exécution qui pilote aussi les types TypeScript - Appeler
value()sur une resource en état d'erreur lève une exception : toujours protéger avechasValue()ou vérifierstatus()d'abord - Toutes les variantes de resource annulent automatiquement les requêtes en cours lorsque les dépendances changent, évitant les conditions de course sans logique d'annulation manuelle
Tu saurais repérer le bug en Angular ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 13 octobre 2026
Tags
Partager
Articles similaires

RxJS dans Angular 2026 : operators, Subjects et interop Signals
RxJS dans Angular 2026 : maîtriser les operators, les Subjects et les patterns d'interop Signals utilisés en production, ainsi que les questions d'entretien les plus fréquentes.

Injection de Dépendances Angular Avancée en 2026 : Providers, Tokens et Questions d'Entretien
Maîtriser l'injection de dépendances Angular avec InjectionToken, les injecteurs hiérarchiques, les modificateurs de résolution et les multi-providers. Inclut des questions d'entretien et des patterns de production.

Syntaxe de flux de contrôle Angular en 2026 : @if, @for, @switch et questions d'entretien
Guide complet sur la syntaxe de flux de contrôle Angular avec @if, @for et @switch. Apprenez à remplacer les directives structurelles par les blocs de contrôle modernes et préparez-vous aux entretiens techniques.