Photo by Honglin Shaw on Unsplash
Comment créer facilement un composant input réutilisable piloté par l'interface ControlValueAccessor ?
L'une des problématiques majeures en Angular est de créer des composants réutilisables ce qui constitue l'essence même du framework. Souvent, nous sommes amenés à créer des composants input
pour les utiliser un peu partout dans nos projets suivant le besoin. Ces composants input
doivent être fonctionnels lorsque nous mettons en place un formulaire piloté par un Template-Driven Form ou un Reactive Form.
Le but de cet article est de mettre la lumière sur comment créer facilement un composant input
réutilisable que nous pourrions utiliser dans nos formulaires réactifs.
Etape 1 : Création du composant app-input
Commençons par générer un nouveau composant avec la commande Angular CLI :
ng generate component input
Nous modifierons le template de notre composant pour avoir ceci de façon très basique :
<input type="text" [formControl]="control"/>
Etape 2: Implémentation de ControlValueAccessor
Pour que notre composant puisse être utilisé dans un formulaire réactif ou un formulaire basé sur des modèles, nous devons implémenter l'interface ControlValueAccessor
.
Nous avons besoin de l'interface ControlValueAccessor
parce que lorsqu'on crée le composant et qu'on désire l'utiliser dans un composant parent, nous ne devons pas oublier qu'il sera encapsulé dans son sélecteur app-input
(dans notre cas ici). Ainsi, tous les attributs auxquels l'on avait accès directement sur un input
ne seront plus disponibles. C'est davantage plus difficile si l'on veut avoir le contrôle sur notre composant via un formulaire réactif ou un formulaire basé sur des modèles.
Cette interface fournit une série de méthodes que nous pouvons utiliser pour synchroniser la valeur de notre composant avec un FormControl.
Nous pouvons le faire en ajoutant les méthodes suivantes à notre composant :
onChange: any = () => {};
onTouched = () => {};
writeValue(value: any): void {
// mettre à jour la valeur de votre composant
}
registerOnChange(fn: any): void {
// enregistrer une fonction qui sera appelée lorsque la valeur du composant change
this.onChange = fn;
}
registerOnTouched(fn: any): void {
// enregistrer une fonction qui sera appelée lorsque le composant est touché
this.onTouched = fn;
}
Lorsqu'on implémente l'interface ControlValueAccessor
, ces trois méthodes sont automatiquement redéfinies dans notre composant. Ces méthodes permettront à notre composant d'écouter les changements et de les enregistrer suivant le besoin.
Il faut aussi penser à rendre accessible l'objet AbstractControl
afin de pouvoir effectuer des manipulations dans le template HTML
de notre composant pour soit afficher des messages d'erreur ou gérer des scénarios. Voici ce qu'il faut faire :
...
public control: AbstractControl;
...
constructor(private injector: Injector) {}
ngAfterViewInit(): void {
const ngControl = this.injector.get(NgControl, null);
if (ngControl) {
setTimeout(() => {
this.control = Object.assign(new FormControl(), ngControl.control) as FormControl;
});
}
}
Cette initialisation dans la méthode ngAfterViewInit
nous permet d'accéder facilement à l'objet AbstractControl
de notre composant input
et de prévenir un problème lié à l'appel de notre composant dans un composant parent. En effet, le setTimeout
est utilisé pour s'assurer que le DOM
a été bien initialisé par le parent pour permettre l'utilisation de notre composant enfant.
Nous devons également fournir un provider pour notre composant qui indique à Angular d'utiliser notre composant comme un ControlValueAccessor
:
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
}
]
Etape 3 : Utilisation du composant dans un formulaire
Maintenant que notre composant est un ControlValueAccessor
, nous pouvons l'utiliser dans un formulaire réactif. Voici un exemple :
<form [formGroup]="form">
<app-input formControlName="firstname"></app-input>
</form>
Nous pouvons obtenir la valeur du composant en utilisant form.get('firstname').value
et nous pouvons définir la valeur du composant en utilisant form.get('firstname').setValue(value)
.
Est-ce que ça fonctionne aussi avec
[(ngModel)]
?
La question de savoir si ngModel
est pris en compte par le ControlValueAccessor
est très récurrente !
La réponse est OUI
. Nous pouvons utiliser ngModel
sur un composant piloté par ControlValueAccessor
et ça fonctionne parfaitement :
<app-input [(ngModel)]="value"></app-input>
En somme, lorsque nous décidons de créer des composants réutilisables de type input
, pensons toujours à implémenter l'interface ControlValueAccessor
pour avoir cette flexibilité à pouvoir l'utiliser dans un formulaire réactif (Reactive Form) ou un formulaire basé sur des modèles (Template-Driven Form).
Pour plus d'informations :