Angular 20 en 2026 : Resource API, httpResource et questions d'entretien

Angular 20 introduit httpResource et stabilise la Resource API pour la récupération de données fondée sur les signals. Un tutoriel pratique sur resource(), rxResource(), httpResource(), la validation Zod et les questions d'entretien courantes.

Tutoriel Angular 20 Resource API et httpResource pour la récupération réactive de données avec les signals

Angular 20 place la Resource API et httpResource au cœur de la récupération de données fondée sur les signals. Ces API expérimentales remplacent les schémas de souscription verbeux de HttpClient par des primitives réactives qui suivent automatiquement les états de chargement, d'erreur et de résolution.

Ce qui change dans Angular 20

La Resource API renomme request en params et loader en stream (pour rxResource). Les valeurs de statut sont désormais des littéraux de chaîne ('idle', 'loading', 'resolved', 'error', 'reloading', 'local') au lieu d'énumérations numériques. httpResource s'appuie sur HttpClient et prend en charge nativement les interceptors et la validation Zod.

Les trois variantes de Resource dans Angular 20

Angular 20 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 pour fetch() ou toute API fondée sur les promesses.
  • rxResource() fonctionne avec les Observables. Le bon choix lorsque des opérateurs RxJS comme debounceTime, retry ou switchMap sont nécessaires.
  • httpResource() encapsule directement le HttpClient d'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.

user-profile.component.tstypescript
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.

product-list.component.tstypescript
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
  }
}

Plusieurs détails comptent ici. 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 réservé à la lecture

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.

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.

order.component.tstypescript
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 dans Angular 20

Angular 20 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.

search.component.tstypescript
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($any($event.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

Angular 20 transforme ResourceStatus, qui passe d'une énumération numérique à 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() |

status-demo.component.tstypescript
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.

Accéder à value() en état d'erreur

Depuis Angular 20, 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 cassant par rapport à Angular 19, où value() retournait undefined en cas d'erreur.

Questions d'entretien Angular 20 sur httpResource

La Resource API devient un sujet incontournable 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.

Pour approfondir les signals Angular et leur intégration dans le framework au sens large, le module 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 :

typescript
// 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.

Conclusion

  • httpResource remplace les souscriptions manuelles à HttpClient par 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, et httpResource() pour les appels HTTP standard avec prise en charge des interceptors
  • Angular 20 renomme request en params et loader en stream dans rxResource — mettre à jour le code existant en conséquence
  • Les valeurs de statut sont désormais des littéraux de chaîne ('idle', 'loading', 'resolved', 'error', 'reloading', 'local'), remplaçant les énumérations numériques d'Angular 19
  • L'option parse de httpResource intè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 dans Angular 20 — toujours protéger avec hasValue() ou vérifier status() 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

Tags

#angular
#angular-20
#resource-api
#httpresource
#signals
#typescript
#tutorial

Partager

Articles similaires