Commandes personnalisées en angulaire

Prologue


Parlons des formulaires réactifs en angulaire, apprenons pour les contrôles personnalisés comment les créer, les utiliser et les valider. L'article suppose que vous connaissez déjà le cadre angulaire, mais que vous souhaitez approfondir ses spécificités. Bon voeu, commençons.

Formulaires réactifs et pilotés par modèle


Quelques mots pour être sûr que nous sommes sur la même longueur d'onde. Un angulaire a deux types de formulaires: les formulaires pilotés par modèle et les formulaires réactifs .

Les formulaires pilotés par modèle sont des formulaires basés sur une liaison bidirectionnelle (hi, angularjs). Nous spécifions le champ dans la classe (par exemple, nom d'utilisateur), dans le code HTML de la balise d'entrée, nous lions [(valeur)] = "nom d'utilisateur", et lorsque la valeur d'entrée change, la valeur du nom d'utilisateur change. En 2011, c'était sacrément magique! D'accord, mais il y a une nuance. De cette façon, la construction de formes complexes sera ... difficile.

Les formulaires réactifs sont un outil pratique pour créer des formulaires simples et complexes. Ils nous disent de "créer une instance de la classe de formulaire ( FormGroup ), de lui passer des instances des contrôles ( FormControl ) et de simplement la donner en html, et je ferai le reste". Ok, essayons:

class UsefullComponent { public control = new FormControl(''); public formG = new FormGroup({username: control}); } 

 <form [formGroup]="formG"> <input type="text" formControlName="username"> </form> 

En conséquence, nous obtenons une forme réactive, avec le blackjack et ... enfin, vous comprenez, avec toutes les commodités. Par exemple, formG.valueChanges nous donnera un observable (flux) de changements de formulaire. Vous pouvez également ajouter de nouveaux contrôles, supprimer ceux existants, modifier les règles de validation, obtenir la valeur du formulaire ( formG.value ) et bien plus encore. Et tout cela avec une seule instance de formG .

Et pour ne pas créer manuellement des instances des classes ci-dessus à chaque fois, les développeurs de angular nous ont donné un FormBuilder pratique à l'aide duquel l'exemple ci-dessus peut être réécrit comme ceci:

 class UsefullComponent { public formG: FormGroup; constructor(private fb: FormBuilder) { this.formG = fb.group({ name: '' //  new FormControl() !! }) } } 

Contrôles personnalisés


Angular nous dit "bro, si vous n'avez pas assez de contrôles natifs (entrée, date, nombre et autres), ou si vous ne les aimez pas du tout pour quelque chose, voici un outil simple mais très puissant pour créer le vôtre." Les contrôles personnalisés peuvent être utilisés même avec des formulaires réactifs, même avec des modèles, et ils sont implémentés à l'aide de directives (surpris!). Sachant que les composants sont des directives avec un modèle (ce dont vous avez besoin), nous écrivons:

 import { Component, Input } from '@angular/core'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent { @Input() value = 0; up() { this.value++; } down() { this.value - ; } } 

Comme toute directive, cela doit être déclaré dans le module (pour que l'article ne se développe pas, nous omettons de telles nuances).

Maintenant, nous pouvons utiliser le composant créé:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control></counter-control> ` }) class ParentComponent {} 

Tout fonctionne bien sûr, mais où sont les formulaires?! Au moins basé sur des modèles ....
Pas de panique, tout se passera après avoir rencontré ControlValueAccessor .

Accesseur de valeur de contrôle


Pour que les mécanismes angulaires interagissent avec votre contrôle personnalisé, vous avez besoin de ce contrôle pour implémenter une interface spécifique. Cette interface est appelée ControlValueAccessor . Ceci est un assez bon exemple de polymorphisme de la POO, lorsque notre objet (dans ce cas, le contrôle) implémente une interface et que d'autres objets (formes angulaires) interagissent avec notre objet via cette interface.

Grâce à ControlValueAccessor , nous avons une seule façon de travailler avec les contrôles, qui, incidemment, n'est pas seulement utilisée pour créer des contrôles personnalisés. Angulaire sous le capot utilise également cette interface. Pour quoi? Et juste pour apporter à un look uniforme le comportement des contrôles natifs. Par exemple, en entrée, la valeur est contenue dans l'attribut value, dans la case à cocher, la valeur est déterminée via l'attribut vérifié. Ainsi, chaque type de contrôle a son propre ControlValueAccessor: DefaultValueAccessor pour les entrées et la zone de texte, CheckboxControlValueAccessor pour les cases à cocher, RadioControlValueAccessor pour les boutons radio, etc.

Mais que fait angular en utilisant ControlValueAccessor ? Tout est assez simple, il écrit la valeur du modèle dans le DOM (vue) et déclenche également l'événement de contrôle des modifications dans FormGroup et d'autres directives.

Maintenant que nous avons découvert ControlValueAccessor , nous pouvons l'appliquer à notre contrôle.

Regardons son interface:

 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void } 

writeValue (value: any) - appelé lorsque la source ( new FormControl ('I am default value') ) ou une nouvelle valeur au-dessus de control.setValue ('I am setted value') est définie .

registerOnChange (fn: any) - une méthode qui définit le gestionnaire qui doit être appelé lorsque la valeur change (fn est un rappel qui notifie au formulaire que la valeur a changé dans ce contrôle).

registerOnTouched (fn: any) - définit le rappel qui est appelé sur l'événement de flou (le contrôle est marqué avec touché )

 import { Component, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent implements ControlValueAccessor { @Input() value = 0; onChange(_: any) {} up() { this.value++; } down() { this.value - ; } writeValue(value: any) { this.value = value; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched() {} } 


Pour que le formulaire parent soit au courant des modifications du contrôle, nous devons appeler la méthode onChange pour chaque modification de la valeur. Afin de ne pas écrire un appel onChange dans chaque méthode (de haut en bas), nous implémentons le champ de valeur via des getters et setters:

 // … class CounterControlComponent implements ControlValueAccessor { private _value; get value() { return this._value; } @Input() set value(val) { this._value = val; this.onChange(this._value); } onChange(_: any) {} up() { this.value++; } down() { this.value - ; } // … } 

Pour le moment, l'angulaire ne sait pas que le composant implémentant ControlValueAccessor doit être considéré comme un contrôle personnalisé, parlons-en:

 import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ … providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true }] }) class CounterControlComponent implements ControlValueAccessor { … } 

Dans cette section du code, nous disons à l'angulaire «Je veux enregistrer un nouveau contrôle personnalisé ( fournissez: NG_VALUE_ACCESSOR ), utilisez l'instance existante (à ce moment notre composant sera initialisé) du composant ( useExisting: forwardRef (() => CounterControlComponent)) .

multi: true indique qu'il peut y avoir plusieurs dépendances avec un tel jeton ( NG_VALUE_ACCESSOR ).

Pas seulement créé


Il est temps de zayuzat notre contrôle personnalisé. N'oubliez pas d'ajouter FormsModule / ReactiveFormsModule aux importations du module où ce contrôle est utilisé.

Utilisation dans les formulaires pilotés par modèle


Tout est simple ici, en utilisant la liaison bidirectionnelle via ngModel, nous obtenons un changement de vue lorsque le modèle change et vice versa:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control [(ngModel)]="controlValue"></counter-control> ` }) class ParentComponent { controlValue = 10; } 

Utilisation sous formes réactives


Comme indiqué au début de l'article, créez une instance de la forme réactive via FormBuilder , effectuez le rendu en html et profitez:

 import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'parent-component', template: ` <form [formGroup]="form"> <counter-control formControlName="counter"></counter-control> </form> ` }) class ParentComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ counter: 5 }); } } 

Maintenant, c'est une forme réactive à part entière avec nos contrôles personnalisés, tandis que tous les mécanismes fonctionnent de la même manière qu'avec les contrôles natifs (ai-je mentionné le polymorphisme?). Afin de ne pas charger l'article actuel, nous parlerons de la validation et de l'affichage des erreurs de contrôle personnalisées dans le prochain article.

Matériaux:

Article sur les contrôles personnalisés de Pascal Precht.
Des quais se forment dans l'angulaire.
Une série d'articles sur rxjs.

Source: https://habr.com/ru/post/fr481424/


All Articles