📧 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://www.easyangularkit.com/easyangularkit

📧 Reste informé(e) !

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

S'inscrire gratuitement

AngularKit

Suite d'outils pour développeurs Angular francophones. Apprends, modernise tes réflexes, audite ta codebase.

Produits

Contact

Légal

© 2026 AngularKit. Tous droits réservés.