Cours Angular · Module 7: RxJS
Patterns RxJS pratiques dans Angular
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'usage | Signals | RxJS |
|---|---|---|
| État du composant | oui | non |
| Calculs dérivés simples | oui, computed() | non |
| Requêtes HTTP | non | oui |
| Debounce/throttle | non | oui |
| Transformations multiples | non | oui |
| Combinaison de flux | non | oui |
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. Quand forkJoin émet-il une valeur ?
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)