📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

~2 min de lecture

Le double subscribe, c’est un grand classique chez les devs Angular.
Souvent invisible, toujours piégeux.
Voici 5 façons progressives de faire mieux, en partant du pire pour finir par le plus moderne.


❌ 1) Le mauvais exemple — double subscribe (avec inject)

Une version fonctionnelle, mais dangereuse à long terme.

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { ReplaySubject } from 'rxjs';

type User = { id: string; name: string };

@Component({
  selector: 'user-page-bad',
  template: `
    @if (user) {
      <p>{{ user.name }}</p>
    } @else {
      <p>Chargement…</p>
    }
  `,
})
export class UserPageBad {
  private readonly route = inject(ActivatedRoute);

  private readonly http = inject(HttpClient);

  protected readonly user: User | null = null;

  ngOnInit() {
    // Premier subscribe : écoute de la route
    this.route.paramMap.subscribe(params => {
      const id = params.get('id');
      if (!id) return;

      // Second subscribe imbriqué : appel HTTP (anti-pattern)
      this.http.get<User>(`/api/users/${id}`).subscribe(user => {
        this.user = user;
      });
    });
  }
}

Problèmes :

  • Souscriptions imbriquées → pyramide d’enfer.
  • Nettoyage compliqué (unsubscribe obligatoire).
  • Risque de requêtes multiples ou de fuites mémoire.
  • Pas facilement compréhensible.

✅ 2) Version RxJS propre — la baseline switchMap

L’équivalent “clean” et réactif avec un flux plat et auto-nettoyé.

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { map, switchMap } from 'rxjs/operators';

type User = { id: string; name: string };

@Component({
  selector: 'user-page-rx',
  template: `
    @if (user$ | async; as user) {
      <p>{{ user.name }}</p>
    } @else {
      <p>Chargement…</p>
    }
  `,
})
export class UserPageRx {
  private readonly route = inject(ActivatedRoute);

  private readonly http = inject(HttpClient);

  protected readonly user$ = this.route.paramMap.pipe(
  map(params => params.get('id')!),
  switchMap(id => this.http.get<User>(`/api/users/${id}`))
  );
}

Pourquoi c’est mieux :

  • Zéro subscribe() manuel (| async gère tout).
  • switchMap annule la requête précédente si l’ID change.
  • Code lisible, testable, sans effet de bord.

⚡ 3) Version moderne Signals — rxResource()

À partir d’Angular 19, on peut combiner RxJS et Signals via rxResource().
C’est la version la plus naturelle pour du HttpClient.

import { Component, inject, rxResource } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';

type User = { id: string; name: string };

@Component({
  selector: 'user-page-rx-resource',
  template: `
    @if (userRes.isLoading()) {
      <p>Chargement…</p>
    } @else if (userRes.error(); as e) {
      <p>Oups : {{ e.message }}</p>
    } @else if (userRes.value(); as user) {
      <p>{{ user.name }}</p>
    }
  `,
})
export class UserPageRxResource {
  private readonly route = inject(ActivatedRoute);

  private readonly http = inject(HttpClient);

  // Signal issu de la route
  private readonly id = toSignal(
  this.route.paramMap.pipe(map(p => p.get('id')!)),
  { initialValue: undefined as string | undefined }
  );

  protected readonly userRes = rxResource({
    params: () => this.id(),
    stream: ({ params }) =>
    // ne déclenche pas si undefined
    params ? this.http.get<User>(`/api/users/${params}`) : undefined,
  });
}

Atouts pédagogiques :

  • Liaison directe entre RxJS et Signals.
  • Annulation automatique via AbortSignal.
  • isLoading(), error(), value() intégrés.
  • Pas de subscribe() ni async nécessaire.

💡 4) Version Input + Resource — composant réutilisable

Découple le composant des routes, parfait avec withComponentInputBinding().

import { Component, input, inject, rxResource } from '@angular/core';
import { HttpClient } from '@angular/common/http';

type User = { id: string; name: string };

@Component({
  selector: 'user-by-input',
  template: `
    @if (userRes.isLoading()) {
      <p>Chargement…</p>
    } @else if (userRes.error(); as e) {
      <p>Oups : {{ e.message }}</p>
    } @else if (userRes.value(); as user) {
      <p>{{ user.name }}</p>
    }
  `,
})
export class UserByInput {
  private readonly http = inject(HttpClient);

  // Id fourni par un parent ou une route (data, resolver, params)
  private readonly id = input.required<string>();

  protected readonly userRes = rxResource({
    params: () => this.id(),
    stream: ({ params }) =>
    this.http.get<User>(`/api/users/${params}`),
  });
}

Pourquoi c’est top :

  • Complètement découplé du router.
  • Réutilisable partout.
  • Gestion intégrée des états et annulation.
  • Compatible withComponentInputBinding().

🚀 5) Version ultra-native — httpResource()

import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop';
import { httpResource } from '@angular/common/http';

type User = { id: string; name: string };

@Component({
  selector: 'user-page-http-resource',
  template: `
    @if (userRes.isLoading()) {
      <p>Chargement…</p>
    } @else if (userRes.error(); as e) {
      <p>Oups : {{ e.message }}</p>
    } @else if (userRes.value(); as user) {
      <p>{{ user.name }}</p>
    }
  `,
})
export class UserPageHttpResource {
  private readonly route = inject(ActivatedRoute);

  private readonly id = toSignal(
  this.route.paramMap.pipe(map(p => p.get('id')!)),
  { initialValue: undefined as string | undefined }
  );

  protected readonly userRes = httpResource<User>(() => {
    const id = this.id();
    // ne déclenche pas si undefined
    return id ? `/api/users/${id}` : undefined;
  });
}

Pourquoi c’est cool :

  • Aucun inject(HttpClient) nécessaire.
  • AbortSignal + SSR cache intégrés.
  • API concise : httpResource<User>(() => url).
  • Idéal pour le code “data-first” avec SSR.

🧩 Résumé pédagogique — tableau comparatif

  • Tu es 100% RxJS aujourd’hui ➜ switchMap (baseline).
  • Tu veux RxJS et un état UI “clé en main” ➜ rxResource().
  • Tu veux un composant réutilisable (découplé du Router) ➜ Input + rxResource().
  • Tu vises simplicité HTTPhttpResource().
  • Évite toujours ➜ double subscribe.

No-trigger tip (important) :
Utilise initialValue: undefined dans toSignal(...) et fais retourner undefined par request/factory tant que l’ID n’est pas connu. Ainsi, aucune requête parasite n’est lancée.


Envie de découvrir EasyAngularKit et son programme ?

Découvre EasyAngularKit : https://pim.ms/C31g7p2

📧 Reste informé(e) !

Reçois les derniers articles et conseils EasyAngularKit directement dans ta boîte mail.

S'inscrire gratuitement

EasyAngularKit

Formation complète pour maîtriser Angular et développer des applications web modernes.

Navigation

Contact

Légal

© 2026 Easy Angular Kit. Tous droits réservés.