Les nouveautés d'Angular 19 en 4 minutes

Angular 19 vient de sortir, et il y a beaucoup de nouveautés intéressantes : hydratation incrémentale, linkedSignal, l'API des ressources, et plein d'autres choses. Venez découvrir tout ça en moins de 4 minutes !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Écouter les changements sur FormControl à l'aide d'un Observable

Dans cet article, nous allons voir comment détecter et réagir aux changements de valeur d'un champ de formulaire en utilisant les Observables dans Angular.

Comprendre le concept

Imaginez que vous êtes dans un café et que vous commandez un café. Vous ne voulez pas que le barista commence à préparer votre café à chaque fois que vous changez d'avis ("Un expresso... non, un cappuccino... ah non, finalement un latte..."). Vous préférez qu'il attende que vous ayez fini de réfléchir avant de commencer la préparation.

C'est exactement le même principe avec un champ de recherche : nous voulons éviter de déclencher une recherche à chaque frappe de clavier, mais plutôt attendre que l'utilisateur ait fini de taper avant d'effectuer la recherche.

Implémentation basique

Commençons par une implémentation simple pour comprendre le fonctionnement de base :

ts
import { Component, OnInit } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <input type="text" [formControl]="searchField" placeholder="Rechercher un utilisateur...">
  `
})
export class SearchComponent implements OnInit {
  searchField = new FormControl('');

  ngOnInit() {
    this.searchField.valueChanges.subscribe(value => {
      console.log('Nouvelle valeur:', value);
    });
  }
}

CONSEIL

L'utilisation de valueChanges retourne un Observable qui émet une nouvelle valeur à chaque modification du champ.

Amélioration avec les opérateurs RxJS

Pour optimiser notre recherche, nous allons utiliser deux opérateurs RxJS très utiles :

  1. debounceTime: Attend un certain délai avant d'émettre la valeur
  2. distinctUntilChanged: Évite les émissions successives de valeurs identiques

Voici un exemple complet avec un service de recherche d'utilisateurs :

ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { debounceTime, distinctUntilChanged, Subscription } from 'rxjs';
import { UserService } from '../services/user.service';

@Component({
  selector: 'app-search',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <input type="text" [formControl]="searchField" placeholder="Rechercher un utilisateur...">
    
    @if (users.length) {
      <ul>
        @for (user of users; track user.id) {
          <li>{{ user.name }}</li>
        }
      </ul>
    }
  `
})
export class SearchComponent implements OnInit, OnDestroy {
  private userService = inject(UserService);
  searchField = new FormControl('');
  users: User[] = [];
  private subscription!: Subscription;

  ngOnInit() {
    this.subscription = this.searchField.valueChanges.pipe(
      debounceTime(400),  // Attend 400ms après la dernière frappe
      distinctUntilChanged() // Ignore si la valeur n'a pas changé
    ).subscribe(term => {
      if (term) {
        this.userService.searchUsers(term).subscribe(
          users => this.users = users
        );
      } else {
        this.users = [];
      }
    });
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}
ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from './user';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://api.example.com/users';
  private http = inject(HttpClient);

  searchUsers(term: string): Observable<User[]> {
    return this.http.get<User[]>(`${this.apiUrl}?search=${term}`);
  }
}
ts
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}

ATTENTION

N'oubliez pas de vous désabonner de l'Observable dans ngOnDestroy pour éviter les fuites de mémoire.

Cas d'utilisation avancés

Voici quelques cas d'utilisation plus avancés avec d'autres opérateurs RxJS :

ts
this.searchField.valueChanges.pipe(
  debounceTime(400),
  distinctUntilChanged(),
  filter(term => term.length >= 3), // Ne déclenche la recherche que si au moins 3 caractères
  switchMap(term => this.userService.searchUsers(term)), // Passer à la prochaine requête HTTP
  catchError(error => {
    console.error('Erreur de recherche:', error);
    return of([]); // Retourne un tableau vide en cas d'erreur
  })
).subscribe(users => this.users = users);

BONNES PRATIQUES

  • Utilisez toujours debounceTime pour les champs de recherche en temps réel
  • Pensez à distinctUntilChanged pour éviter les requêtes inutiles
  • Gérez correctement les erreurs avec catchError
  • Utilisez switchMap plutôt que mergeMap pour les requêtes HTTP. Si l'observable est arrêté, la requête est annulée.

Conclusion

L'utilisation des Observables avec les FormControl permet de créer des interfaces utilisateur réactives et performantes. Les opérateurs RxJS comme debounceTime et distinctUntilChanged sont essentiels pour optimiser les performances et l'expérience utilisateur.

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !