Cours Angular · Module 7: RxJS

Patterns RxJS pratiques dans Angular

2 min de lecture
Dans cette leçon5

Recherche avec Debounce

1@Component({
2  template: `
3    <input #searchInput placeholder="Search for a samurai...">
4    @for (result of searchResults$ | async; track result.id) {
5      <div>{{ result.name }}</div>
6    }
7  `
8})
9export class SearchComponent implements AfterViewInit {
10  @ViewChild('searchInput') searchInput!: ElementRef;
11  searchResults$!: Observable<Samurai[]>;
12
13  private http = inject(HttpClient);
14
15  ngAfterViewInit(): void {
16    this.searchResults$ = fromEvent<InputEvent>(
17      this.searchInput.nativeElement,
18      'input'
19    ).pipe(
20      map(event => (event.target as HTMLInputElement).value),
21      debounceTime(300),           // Attend 300ms
22      distinctUntilChanged(),       // Ignore les valeurs identiques
23      filter(query => query.length >= 2),  // Minimum 2 caractères
24      switchMap(query =>            // Annule la requête précédente
25        this.http.get<Samurai[]>(`/api/search?q=${query}`).pipe(
26          catchError(() => of([]))  // Gestion des erreurs
27        )
28      )
29    );
30  }
31}

Polling avec Retry

1@Injectable({ providedIn: 'root' })
2export class StatusService {
3  private http = inject(HttpClient);
4
5  getStatusWithPolling(): Observable<Status> {
6    return interval(5000).pipe(  // Toutes les 5 secondes
7      startWith(0),              // Immédiatement au démarrage
8      switchMap(() =>
9        this.http.get<Status>('/api/status').pipe(
10          retry({
11            count: 3,
12            delay: 1000  // Réessaie 3 fois toutes les secondes
13          }),
14          catchError(error => {
15            console.error('Status check failed:', error);
16            return of({ status: 'unknown', error: true });
17          })
18        )
19      ),
20      shareReplay(1)  // Partage le résultat entre les abonnés
21    );
22  }
23}

Chargement avec Cache

1@Injectable({ providedIn: 'root' })
2export class CachedDataService {
3  private http = inject(HttpClient);
4  private cache$ = new Map<string, Observable<any>>();
5
6  getData<T>(url: string): Observable<T> {
7    if (!this.cache$.has(url)) {
8      this.cache$.set(url,
9        this.http.get<T>(url).pipe(
10          shareReplay({ bufferSize: 1, refCount: true }),
11          tap({
12            error: () => this.cache$.delete(url)
13          })
14        )
15      );
16    }
17    return this.cache$.get(url)!;
18  }
19
20  invalidateCache(url: string): void {
21    this.cache$.delete(url);
22  }
23}

Gérer plusieurs requêtes dépendantes

1getSamuraiWithWeapons(id: number): Observable<SamuraiWithWeapons> {
2  return this.http.get<Samurai>(`/api/samurai/${id}`).pipe(
3    switchMap(samurai =>
4      forkJoin({
5        weapons: this.http.get<Weapon[]>(`/api/samurai/${id}/weapons`),
6        skills: this.http.get<Skill[]>(`/api/samurai/${id}/skills`)
7      }).pipe(
8        map(({ weapons, skills }) => ({
9          ...samurai,
10          weapons,
11          skills
12        }))
13      )
14    )
15  );
16}

Signals vs RxJS - Lequel utiliser ?

Cas d'usageSignalsRxJS
État du composantouinon
Calculs dérivés simplesoui, computed()non
Requêtes HTTPnonoui
Debounce/throttlenonoui
Transformations multiplesnonoui
Combinaison de fluxnonoui
1// Intégration - le meilleur des deux mondes
2@Component({...})
3export class HybridComponent {
4  private http = inject(HttpClient);
5
6  // Signal pour l'état de l'UI
7  isLoading = signal(false);
8  selectedId = signal<number | null>(null);
9
10  // RxJS pour le HTTP
11  samurai$ = toObservable(this.selectedId).pipe(
12    filter(id => id !== null),
13    tap(() => this.isLoading.set(true)),
14    switchMap(id => this.http.get<Samurai>(`/api/samurai/${id}`)),
15    tap(() => this.isLoading.set(false))
16  );
17
18  // Observable → Signal
19  samurai = toSignal(this.samurai$);
20}

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quand forkJoin émet-il une valeur ?

  2. 2. À quoi sert l'opérateur shareReplay() dans les patterns RxJS pratiques ?

Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Complète le code qui envoie trois requêtes HTTP en parallèle et ne réagit que lorsqu'elles sont toutes terminées.

  • Tri vertical

    Ordonne les opérateurs d'une chaîne de recherche complète avec debounce, dans leur ordre à l'intérieur de pipe() :

  • Tri horizontal

    Ordonne la conversion d'un Observable en Signal : samurai = toSignal(this.samurai$)

  • Clique dans l'ordre

    Ordonne l'appel qui convertit un Signal en Observable : toObservable(this.selectedId)

Articles utiles