Appearance
Comment créer une fonction personnalisée de validation asynchrone ?
Voici comment vous pourriez créer une fonction de validation asynchrone qui vérifie si une adresse email existe déjà dans une base de données, sur Angular :
Créez une fonction dans votre composant Angular qui prendra en paramètre une adresse email et retournera un Observable. Cette fonction enverra une requête HTTP à votre backend pour vérifier si l'adresse email existe déjà dans la base de données.
Utilisez la méthode
asyncValidator
deAbstractControl
pour créer un validateur asynchrone personnalisé. Cette méthode prend en paramètre une fonction qui retourne un Observable et retourne également un Observable.Dans la fonction passée à
asyncValidator
, appelez la fonction que vous avez créée dans l'étape 1 en lui passant l'adresse email à vérifier. Transformez le résultat de cette fonction en un Observable qui émet une erreur si l'adresse email existe déjà, ou qui ne produit aucune valeur si l'adresse email n'existe pas.Ajoutez le validateur asynchrone à votre formulaire en utilisant la méthode
setAsyncValidators
deFormControl
ouFormGroup
.
Voici un exemple de code qui montre comment mettre en œuvre cette approche :
ts
import { Observable, timer, map, switchMap } from 'rxjs';
import { AbstractControl, AsyncValidatorFn } from '@angular/forms'
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class EmailValidatorService {
constructor(private http: HttpClient) {}
checkEmailExists(email: string): Observable<boolean> {
return this.http.get<boolean>(`/api/email-exists?email=${email}`);
}
checkEmailExistsAsync(): Observable<{ emailExists: boolean | null }> {
return timer(500).pipe(
switchMap(() => this.checkEmailExists(control.value)),
map(exists => (exists ? { emailExists: true } : null))
);
}
}
ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, Validators } from '@angular/forms';
import { EmailValidatorService } from 'email-validator.service'
@Component({
// ...
})
export class MyComponent {
form: FormGroup = this.fb.group({
email: ['', [Validators.required], this.emailValidatorService.checkEmailExistsAsync.bind(this.emailValidatorService)]
});
constructor(private emailValidatorService: EmailValidatorService) {}
}
Dans cet exemple, nous avons une classe de composant Angular qui contient une fonction checkEmailExists
qui envoie une requête HTTP à l'aide de l'objet HttpClient
pour vérifier si une adresse email donnée existe déjà dans une base de données.
Nous avons également une fonction emailExistsValidator
qui utilise la méthode asyncValidator
de AbstractControl
pour créer un validateur asynchrone personnalisé. Cette fonction prend en paramètre un contrôle de formulaire et retourne un Observable qui émet une erreur si l'adresse email existe déjà, ou qui ne produit aucune valeur si l'adresse email n'existe pas.
La fonction emailExistsValidator
utilise la méthode timer
de RxJS pour ajouter un délai de 500 millisecondes avant de vérifier si l'adresse email existe déjà en utilisant la fonction checkEmailExists
. Cela permet d'éviter d'envoyer trop de requêtes HTTP si l'utilisateur tape rapidement dans le champ de formulaire.
Attention avec this
En JavaScript, la valeur de this
dans une fonction dépend du contexte d'exécution, et non de la manière dont ou de l'endroit où la fonction est définie. Ainsi, lorsqu'une méthode est extraite d'un objet et passée ailleurs, elle peut perdre son contexte d'origine.
La méthode .bind()
permet de créer une nouvelle fonction où this
est fixé à la valeur que vous fournissez. Cela vous permet de "verrouiller" la valeur de this
pour une fonction, quel que soit le contexte d'exécution.
Donc, lorsque vous faites:
ts
this.emailValidatorService.checkEmailExistsAsync.bind(this.emailValidatorService)
Vous créez essentiellement une nouvelle version de checkEmailExistsAsync
où this
est toujours this.emailValidatorService
, garantissant ainsi que la méthode a accès à toutes les propriétés et méthodes de EmailExistsValidatorService
.
En utilisant une promesse
Voici comment vous pourriez réécrire l'exemple précédent pour utiliser une promesse au lieu d'un Observable :
ts
import { Observable, timer, map, switchMap, lastValueFrom } from 'rxjs';
import { AbstractControl, AsyncValidatorFn } from '@angular/forms'
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class EmailValidatorService {
constructor(private http: HttpClient) {}
checkEmailExists(email: string): Promise<boolean> {
return lastValueFrom(this.http.get<boolean>(`/api/email-exists?email=${email}`))
}
async emailExistsValidatorAsync(): Promise<{ emailExists: boolean | null }> {
await lastValueFrom(timer(500))
const exists = await this.checkEmailExists(control.value);
return exists ? { emailExists: true } : null;
}
}
Le lastValueFrom
est une fonction de la bibliothèque rxjs
qui est utilisée pour convertir un Observable en une Promesse. Il attend que l'Observable soit terminé (c'est-à-dire qu'il ait émis toutes ses valeurs et complété) puis renvoie la dernière valeur émise par cet Observable comme le résultat de la Promesse.
Cela permet d'utiliser des Observables dans un contexte qui attend des Promesses, comme les fonctions asynchrones avec await
.
Comprendre facilement lastValueFrom
Imaginez que vous avez une radio qui joue plusieurs chansons l'une après l'autre, et que vous voulez connaître la dernière chanson jouée. L'Observable serait comme cette radio, et lastValueFrom
serait comme un ami qui écoute toute la radio pour vous, puis vous dit quelle était la dernière chanson jouée une fois que la radio s'arrête.
En d'autres termes, lastValueFrom
vous permet d'attendre qu'un Observable ait terminé et de récupérer sa dernière valeur, le tout dans un format de Promesse qui est souvent plus facile et plus familier pour les développeurs travaillant avec des opérations asynchrones en JavaScript et TypeScript.