Travailler avec les formulaires angulaires 4 : imbrication et validation des entrées

Publié: 2022-03-11

Sur le Web, certains des premiers éléments d'entrée utilisateur étaient un bouton, une case à cocher, une entrée de texte et des boutons radio. À ce jour, ces éléments sont encore utilisés dans les applications Web modernes, même si le standard HTML a parcouru un long chemin depuis sa définition initiale et permet désormais toutes sortes d'interactions fantaisistes.

La validation des entrées utilisateur est une partie essentielle de toute application Web robuste.

Les formulaires dans les applications angulaires peuvent agréger l'état de toutes les entrées qui se trouvent sous ce formulaire et fournir un état global comme l'état de validation du formulaire complet. Cela peut être très pratique pour décider si l'entrée de l'utilisateur sera acceptée ou rejetée sans vérifier chaque entrée séparément.

Validation d'entrée de formulaires angulaires 4

Dans cet article, vous apprendrez comment vous pouvez travailler avec des formulaires et effectuer facilement une validation de formulaire dans votre application Angular.

Dans Angular 4, il existe deux types de formulaires différents avec lesquels travailler : les formulaires basés sur des modèles et les formulaires réactifs. Nous allons parcourir chaque type de formulaire en utilisant le même exemple pour voir comment les mêmes choses peuvent être implémentées de différentes manières. Plus tard, dans l'article, nous examinerons une nouvelle approche sur la façon de configurer et de travailler avec des formulaires imbriqués.

Formes angulaires 4

Dans Angular 4, les quatre statuts suivants sont couramment utilisés par les formulaires :

  • valide - état de la validité de tous les contrôles de formulaire, vrai si tous les contrôles sont valides

  • invalide – inverse de valid ; vrai si un contrôle est invalide

  • vierge - donne un statut sur la "propreté" du formulaire ; vrai si aucun champ n'a été modifié

  • sale – inverse de pristine ; true si un contrôle a été modifié

Examinons un exemple basique de formulaire :

 <form> <div> <label>Name</label> <input type="text" name="name"/> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear"/> </div> <div> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country"/> </div> <div> <label>City</label> <input type="text" name="city"/> </div> </div> <div> <h3>Phone numbers</h3> <div> <label>Phone number 1</label> <input type="text" name="phoneNumber[1]"/> <button type="button">remove</button> </div> <button type="button">Add phone number</button> </div> <button type="submit">Register</button> <button type="button">Print to console</button> </form>

La spécification de cet exemple est la suivante :

  • nom - est obligatoire et unique parmi tous les utilisateurs enregistrés

  • birthYear - doit être un nombre valide et l'utilisateur doit avoir au moins 18 ans et moins de 85 ans

  • pays - est obligatoire, et juste pour compliquer un peu les choses, nous avons besoin d'une validation que si le pays est la France, alors la ville doit être Paris (disons que notre service est proposé uniquement à Paris)

  • phoneNumber - chaque numéro de téléphone doit suivre un modèle spécifié, il doit y avoir au moins un numéro de téléphone et l'utilisateur est autorisé à ajouter un nouveau numéro de téléphone ou à supprimer un numéro de téléphone existant.

  • Le bouton "Enregistrer" n'est activé que si toutes les entrées sont valides et, une fois cliqué, il soumet le formulaire.

  • Le "Imprimer sur la console" imprime simplement la valeur de toutes les entrées sur la console lorsque vous cliquez dessus.

Le but ultime est de mettre pleinement en œuvre la spécification définie.

Formulaires basés sur des modèles

Les formulaires basés sur des modèles sont très similaires aux formulaires dans AngularJS (ou Angular 1, comme certains l'appellent). Ainsi, quelqu'un qui a travaillé avec des formulaires dans AngularJS sera très familier avec cette approche de travail avec des formulaires.

Avec l'introduction de modules dans Angular 4, il est imposé que chaque type de formulaire spécifique se trouve dans un module séparé et nous devons définir explicitement quel type allons-nous utiliser en important le module approprié. Ce module pour les formulaires basés sur des modèles est FormsModule. Cela étant dit, vous pouvez activer les formulaires basés sur des modèles comme suit :

 import {FormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}

Comme présenté dans cet extrait de code, nous devons d'abord importer le module de navigateur car il "fournit des services essentiels pour lancer et exécuter une application de navigateur". (à partir de la documentation Angular 4). Ensuite, nous importons le FormsModule requis pour activer les formulaires basés sur des modèles. Et la dernière est la déclaration du composant racine, AppComponent, où, dans les prochaines étapes, nous implémenterons le formulaire.

Gardez à l'esprit que dans cet exemple et les exemples suivants, vous devez vous assurer que l'application est correctement démarrée à l'aide de la méthode platformBrowserDynamic .

 import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule);

Nous pouvons supposer que notre AppComponent (app.component.ts) ressemble à ceci :

 import {Component} from '@angular/core' @Component({ selector: 'my-app', templateUrl: 'src/app.component.tpl.html' }) export class AppComponent { }

Où le modèle de ce composant se trouve dans app.component.tpl.html et nous pouvons copier le modèle initial dans ce fichier.

Notez que chaque élément d'entrée doit avoir l'attribut name pour être correctement identifié dans le formulaire. Bien que cela ressemble à un simple formulaire HTML, nous avons déjà défini un formulaire pris en charge par Angular 4 (peut-être que vous ne le voyez pas encore). Lorsque le FormsModule est importé, Angular 4 détecte automatiquement un élément HTML de form et attache le composant NgForm à cet élément (par le selector du composant NgForm). C'est le cas dans notre exemple. Bien que ce formulaire Angular 4 soit déclaré, à ce stade, il ne connaît aucune entrée prise en charge par Angular 4. Angular 4 n'est pas si invasif pour enregistrer chaque élément HTML d' input sur l'ancêtre de form le plus proche.

La clé qui permet à un élément d'entrée d'être remarqué comme un élément Angular 4 et enregistré dans le composant NgForm est la directive NgModel. Ainsi, nous pouvons étendre le modèle app.component.tpl.html comme suit :

 <form> .. <input type="text" name="name" ngModel> .. <input type="text" name="birthYear" ngModel > .. <input type="text" name="country" ngModel/> .. <input type="text" name="city" ngModel/> .. <input type="text" name="phoneNumber[1]" ngModel/> </form>

En ajoutant la directive NgModel, toutes les entrées sont enregistrées dans le composant NgForm. Avec cela, nous avons défini un formulaire Angular 4 entièrement fonctionnel et jusqu'à présent, tout va bien, mais nous n'avons toujours pas de moyen d'accéder au composant NgForm et aux fonctionnalités qu'il offre. Les deux principales fonctionnalités offertes par NgForm sont :

  • Récupération des valeurs de tous les contrôles d'entrée enregistrés

  • Récupération de l'état global de tous les champs

Pour exposer le NgForm, nous pouvons ajouter ce qui suit à l'élément <form> :

 <form #myForm="ngForm"> .. </form>

Ceci est possible grâce à la propriété exportAs du décorateur Component .

Une fois cela fait, nous pouvons accéder aux valeurs de tous les contrôles d'entrée et étendre le modèle à :

 <form #myForm="ngForm"> .. <pre>{{myForm.value | json}}</pre> </form>

Avec myForm.value nous accédons aux données JSON contenant les valeurs de toutes les entrées enregistrées, et avec {{myForm.value | json}} {{myForm.value | json}} , nous imprimons joliment le JSON avec les valeurs.

Que se passe-t-il si nous voulons avoir un sous-groupe d'entrées d'un contexte spécifique enveloppé dans un conteneur et un objet séparé dans les valeurs JSON, par exemple, l'emplacement contenant le pays et la ville ou les numéros de téléphone ? Ne vous inquiétez pas, les formulaires basés sur des modèles dans Angular 4 couvrent également cela. La façon d'y parvenir est d'utiliser la directive ngModelGroup .

 <form #myForm="ngForm"> .. <div ngModelGroup="location"> .. </div> </div ngModelGroup="phoneNumbers"> .. <div> .. </form>

Ce qui nous manque maintenant, c'est un moyen d'ajouter plusieurs numéros de téléphone. La meilleure façon de procéder aurait été d'utiliser un tableau, comme meilleure représentation d'un conteneur itérable de plusieurs objets, mais au moment de la rédaction de cet article, cette fonctionnalité n'est pas implémentée pour les formulaires basés sur des modèles. Nous devons donc appliquer une solution de contournement pour que cela fonctionne. La section des numéros de téléphone doit être mise à jour comme suit :

 <div ngModelGroup="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneId of phoneNumberIds; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" #phoneNumber="ngModel" ngModel/> <button type="button" (click)="remove(i); myForm.control.markAsTouched()">remove</button> </div> <button type="button" (click)="add(); myForm.control.markAsTouched()">Add phone number</button> </div>

Le myForm.control.markAsTouched() est utilisé pour que le formulaire soit touched afin que nous puissions afficher les erreurs à ce moment-là. Les boutons n'activent pas cette propriété lorsqu'ils sont cliqués, seulement les entrées. Pour rendre les exemples suivants plus clairs, je n'ajouterai pas cette ligne sur le gestionnaire de clic pour add() et remove() . Imaginez juste qu'il est là. (Il est présent dans les Plunkers.)

Nous devons également mettre à jour AppComponent pour contenir le code suivant :

 private count:number = 1; phoneNumberIds:number[] = [1]; remove(i:number) { this.phoneNumberIds.splice(i, 1); } add() { this.phoneNumberIds.push(++this.count); }

Nous devons stocker un identifiant unique pour chaque nouveau numéro de téléphone ajouté, et dans le *ngFor , suivre les commandes de numéro de téléphone par leur identifiant (j'avoue que ce n'est pas très agréable, mais jusqu'à ce que l'équipe Angular 4 implémente cette fonctionnalité, j'ai peur , c'est le mieux que nous puissions faire)

D'accord, qu'avons-nous jusqu'à présent, nous avons ajouté le formulaire pris en charge par Angular 4 avec des entrées, ajouté un regroupement spécifique des entrées (emplacement et numéros de téléphone) et exposé le formulaire dans le modèle. Mais que se passe-t-il si nous souhaitons accéder à l'objet NgForm dans une méthode du composant ? Nous verrons deux manières de procéder.

Pour la première manière, le NgForm, étiqueté myForm dans l'exemple actuel, peut être passé comme argument à la fonction qui servira de gestionnaire pour l'événement onSubmit du formulaire. Pour une meilleure intégration, l'événement onSubmit est wrapped par un événement spécifique à Angular 4, NgForm nommé ngSubmit , et c'est la bonne façon de procéder si nous voulons exécuter une action lors de la soumission. Ainsi, l'exemple ressemblera maintenant à ceci :

 <form #myForm="ngForm" (ngSubmit)="register(myForm)"> … </form>

Nous devons avoir une méthode correspondante register , implémentée dans AppComponent. Quelque chose comme:

 register (myForm: NgForm) { console.log('Successful registration'); console.log(myForm); }

De cette façon, en tirant parti de l'événement onSubmit, nous n'avons accès au composant NgForm que lorsque submit est exécuté.

La deuxième méthode consiste à utiliser une requête de vue en ajoutant le décorateur @ViewChild à une propriété du composant.

 @ViewChild('myForm') private myForm: NgForm;

Avec cette approche, nous sommes autorisés à accéder au formulaire, que l'événement onSubmit ait été déclenché ou non.

Génial! Nous avons maintenant un formulaire Angular 4 entièrement fonctionnel avec accès au formulaire dans le composant. Mais remarquez-vous qu'il manque quelque chose ? Que se passe-t-il si l'utilisateur saisit quelque chose comme "ceci-n'est-pas-une-année" dans l'entrée "années" ? Oui, vous l'avez compris, nous manquons de validation des entrées et nous en parlerons dans la section suivante.

Validation

La validation est vraiment importante pour chaque application. Nous voulons toujours valider l'entrée de l'utilisateur (nous ne pouvons pas faire confiance à l'utilisateur) pour empêcher l'envoi/l'enregistrement de données invalides et nous devons afficher un message significatif sur l'erreur pour guider correctement l'utilisateur dans la saisie de données valides.

Pour qu'une règle de validation soit appliquée sur une entrée, le validateur approprié doit être associé à cette entrée. Angular 4 propose déjà un ensemble de validateurs communs comme : required , maxLength , minLength

Alors, comment associer un validateur à une entrée ? Eh bien, assez facile; ajoutez simplement la directive validator au contrôle :

 <input name="name" ngModel required/>

Cet exemple rend obligatoire la saisie du « nom ». Ajoutons quelques validations à toutes les entrées de notre exemple.

 <form #myForm="ngForm" (ngSubmit)="actionOnSubmit(myForm)" novalidate> <p>Is "myForm" valid? {{myForm.valid}}</p> .. <input type="text" name="name" ngModel required/> .. <input type="text" name="birthYear" ngModel required pattern="\\d{4,4}"/> .. <div ngModelGroup="location"> .. <input type="text" name="country" ngModel required/> .. <input type="text" name="city" ngModel/> </div> <div ngModelGroup="phoneNumbers"> .. <input type="text" name="phoneNumber[{{phoneId}}]" ngModel required/> .. </div> .. </form>

Remarque : novalidate est utilisé pour désactiver la validation du formulaire natif du navigateur.

Nous avons rendu le "nom" obligatoire, le champ "années" est obligatoire et doit être composé uniquement de chiffres, la saisie du pays est requise et le numéro de téléphone est également requis. De plus, nous imprimons le statut de la validité du formulaire avec {{myForm.valid}} .

Une amélioration de cet exemple serait de montrer également ce qui ne va pas avec l'entrée de l'utilisateur (et pas seulement de montrer l'état général). Avant de continuer à ajouter une validation supplémentaire, je voudrais implémenter un composant d'assistance qui nous permettra d'imprimer toutes les erreurs pour un contrôle fourni.

 // show-errors.component.ts import { Component, Input } from '@angular/core'; import { AbstractControlDirective, AbstractControl } from '@angular/forms'; @Component({ selector: 'show-errors', template: ` <ul *ngIf="shouldShowErrors()"> <li *ngFor="let error of listOfErrors()">{{error}}</li> </ul> `, }) export class ShowErrorsComponent { private static readonly errorMessages = { 'required': () => 'This field is required', 'minlength': (params) => 'The min number of characters is ' + params.requiredLength, 'maxlength': (params) => 'The max allowed number of characters is ' + params.requiredLength, 'pattern': (params) => 'The required pattern is: ' + params.requiredPattern, 'years': (params) => params.message, 'countryCity': (params) => params.message, 'uniqueName': (params) => params.message, 'telephoneNumbers': (params) => params.message, 'telephoneNumber': (params) => params.message }; @Input() private control: AbstractControlDirective | AbstractControl; shouldShowErrors(): boolean { return this.control && this.control.errors && (this.control.dirty || this.control.touched); } listOfErrors(): string[] { return Object.keys(this.control.errors) .map(field => this.getMessage(field, this.control.errors[field])); } private getMessage(type: string, params: any) { return ShowErrorsComponent.errorMessages[type](params); } }

La liste des erreurs s'affiche uniquement s'il existe des erreurs existantes et que l'entrée est touchée ou sale.

Le message pour chaque erreur est recherché dans une carte de messages prédéfinis errorMessages (j'ai ajouté tous les messages à l'avance).

Ce composant peut être utilisé comme suit :

 <div> <label>Birth Year</label> <input type="text" name="birthYear" #birthYear="ngModel" ngModel required pattern="\\d{4,4}"/> <show-errors [control]="birthYear"></show-errors> </div>

Nous devons exposer le NgModel pour chaque entrée et le transmettre au composant qui affiche toutes les erreurs. Vous pouvez remarquer que dans cet exemple, nous avons utilisé un modèle pour vérifier si les données sont un nombre ; que se passe-t-il si l'utilisateur saisit "0000" ? Ce serait une entrée invalide. De plus, il nous manque les validateurs pour un nom unique, l'étrange restriction du pays (si pays='France', alors la ville doit être 'Paris'), le modèle pour un numéro de téléphone correct et la validation qu'au moins un numéro de téléphone existe. C'est le bon moment pour jeter un œil aux validateurs personnalisés.

Angular 4 propose une interface que chaque validateur personnalisé doit implémenter, l'interface Validator (quelle surprise !). L'interface du Validator ressemble essentiellement à ceci :

 export interface Validator { validate(c: AbstractControl): ValidationErrors | null; registerOnValidatorChange?(fn: () => void): void; }

Où chaque implémentation concrète DOIT implémenter la méthode 'validate'. Cette méthode de validate est vraiment intéressante sur ce qui peut être reçu en entrée et ce qui doit être renvoyé en sortie. L'entrée est un AbstractControl, ce qui signifie que l'argument peut être n'importe quel type qui étend AbstractControl (FormGroup, FormControl et FormArray). La sortie de la méthode de validate doit être null ou undefined (pas de sortie) si l'entrée utilisateur est valide, ou renvoyer un objet ValidationErrors si l'entrée utilisateur n'est pas valide. Avec cette connaissance, nous allons maintenant implémenter un validateur personnalisé birthYear .

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, FormControl, Validator, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[birthYear]', providers: [{provide: NG_VALIDATORS, useExisting: BirthYearValidatorDirective, multi: true}] }) export class BirthYearValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } }

Il y a quelques choses à expliquer ici. Tout d'abord, vous remarquerez peut-être que nous avons implémenté l'interface Validator. La méthode de validate vérifie si l'utilisateur a entre 18 et 85 ans à l'année de naissance saisie. Si l'entrée est valide, alors null est renvoyé, sinon un objet contenant le message de validation est renvoyé. Et la dernière et la plus importante partie est de déclarer cette directive en tant que validateur. Cela se fait dans le paramètre "providers" du décorateur @Directive. Ce validateur est fourni comme une valeur du multi-fournisseur NG_VALIDATORS. Aussi, n'oubliez pas de déclarer cette directive dans le NgModule. Et maintenant, nous pouvons utiliser ce validateur comme suit :

 <input type="text" name="birthYear" #year="ngModel" ngModel required birthYear/>

Ouais, aussi simple que ça !

Pour le numéro de téléphone, on peut valider le format du numéro de téléphone comme ceci :

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[telephoneNumber]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumberFormatValidatorDirective, multi: true}] }) export class TelephoneNumberFormatValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } }

Viennent ensuite les deux validations, pour le pays et le nombre de numéros de téléphone. Remarquez quelque chose de commun pour les deux? Les deux nécessitent plus d'un contrôle pour effectuer une validation appropriée. Eh bien, vous vous souvenez de l'interface Validator et de ce que nous en avons dit ? L'argument de la méthode validate est AbstractControl, qui peut être une entrée utilisateur ou le formulaire lui-même. Cela crée la possibilité d'implémenter un validateur qui utilise plusieurs contrôles pour déterminer le statut de validation concret.

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[countryCity]', providers: [{provide: NG_VALIDATORS, useExisting: CountryCityValidatorDirective, multi: true}] }) export class CountryCityValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } }

Nous avons implémenté un nouveau validateur, validateur pays-ville. Vous pouvez remarquer que maintenant, en tant qu'argument, la méthode validate reçoit un FormGroup et à partir de ce FormGroup, nous pouvons récupérer les entrées requises pour la validation. Le reste des choses est très similaire au validateur d'entrée unique.

Le validateur pour le nombre de numéros de téléphone ressemblera à ceci :

 import { Directive } from '@angular/core'; import { NG_VALIDATORS, Validator, FormGroup, ValidationErrors, FormControl } from '@angular/forms'; @Directive({ selector: '[telephoneNumbers]', providers: [{provide: NG_VALIDATORS, useExisting: TelephoneNumbersValidatorDirective, multi: true}] }) export class TelephoneNumbersValidatorDirective implements Validator { validate(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormGroup> form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }

Nous pouvons les utiliser comme ceci :

 <form #myForm="ngForm" countryCity telephoneNumbers> .. </form>

Identique aux validateurs d'entrée, n'est-ce pas ? Juste maintenant appliqué au formulaire.

Vous souvenez-vous du composant ShowErrors ? Nous l'avons implémenté pour fonctionner avec un AbstractControlDirective, ce qui signifie que nous pourrions le réutiliser pour afficher également toutes les erreurs directement associées à ce formulaire. Gardez à l'esprit qu'à ce stade, les seules règles de validation directement associées au formulaire sont Country-city et Telephone numbers (les autres validateurs sont associés aux contrôles spécifiques du formulaire). Pour imprimer toutes les erreurs de formulaire, procédez comme suit :

 <form #myForm="ngForm" countryCity telephoneNumbers > <show-errors [control]="myForm"></show-errors> .. </form>

La dernière chose qui reste est la validation d'un nom unique. C'est un peu différent; pour vérifier si le nom est unique, un appel au back-end est probablement nécessaire pour vérifier tous les noms existants. Ceci est classé comme une opération asynchrone. À cette fin, nous pouvons réutiliser la technique précédente pour les validateurs personnalisés, il suffit de faire en sorte que la validate renvoie un objet qui sera résolu dans le futur (promesse ou observable). Dans notre cas, nous utiliserons une promesse :

 import { Directive } from '@angular/core'; import { NG_ASYNC_VALIDATORS, Validator, FormControl, ValidationErrors } from '@angular/forms'; @Directive({ selector: '[uniqueName]', providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: UniqueNameValidatorDirective, multi: true}] }) export class UniqueNameValidatorDirective implements Validator { validate(c: FormControl): ValidationErrors { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } }

Nous attendons 1 seconde puis renvoyons un résultat. Semblable aux validateurs de synchronisation, si la promesse est résolue avec null , cela signifie que la validation a réussi ; si la promesse est résolue avec autre chose, la validation a échoué. Notez également que maintenant ce validateur est enregistré auprès d'un autre multi-fournisseur, le NG_ASYNC_VALIDATORS . Une propriété utile des formulaires concernant les validateurs asynchrones est la propriété pending . Il peut être utilisé comme ceci :

 <button [disabled]="myForm.pending">Register</button>

Cela désactivera le bouton jusqu'à ce que les validateurs asynchrones soient résolus.

Voici un Plunker contenant le AppComponent complet, le composant ShowErrors et tous les validateurs.

Avec ces exemples, nous avons couvert la plupart des cas d'utilisation de formulaires basés sur des modèles. Nous avons montré que les formulaires basés sur des modèles sont vraiment similaires aux formulaires dans AngularJS (il sera très facile pour les développeurs AngularJS de migrer). Avec ce type de formulaire, il est assez facile d'intégrer des formulaires Angular 4 avec un minimum de programmation, principalement avec des manipulations dans le template HTML.

Formes réactives

Les formulaires réactifs étaient également connus sous le nom de formulaires «pilotés par modèle», mais j'aime les appeler des formulaires «programmatiques», et vous comprendrez bientôt pourquoi. Les formulaires réactifs sont une nouvelle approche de la prise en charge des formulaires Angular 4, donc contrairement aux modèles basés sur des modèles, les développeurs AngularJS ne seront pas familiers avec ce type.

Nous pouvons commencer maintenant, rappelez-vous que les formulaires basés sur des modèles avaient un module spécial ? Eh bien, les formulaires réactifs ont également leur propre module, appelé ReactiveFormsModule et doivent être importés pour activer ce type de formulaires.

 import {ReactiveFormsModule} from '@angular/forms' import {NgModule} from '@angular/core' import {BrowserModule} from '@angular/platform-browser' import {AppComponent} from 'src/app.component'; @NgModule({ imports: [ BrowserModule, ReactiveFormsModule ], declarations: [ AppComponent], bootstrap: [ AppComponent ] }) export class AppModule {}

N'oubliez pas non plus de démarrer l'application.

Nous pouvons commencer avec le même AppComponent et le même modèle que dans la section précédente.

À ce stade, si le FormsModule n'est pas importé (et assurez-vous qu'il ne l'est pas), nous avons juste un élément de formulaire HTML normal avec quelques contrôles de formulaire, pas de magie angulaire ici.

Nous arrivons au point où vous remarquerez pourquoi j'aime appeler cette approche "programmatique". Afin d'activer les formulaires Angular 4, nous devons déclarer l'objet FormGroup manuellement et le remplir avec des contrôles comme celui-ci :

 import { FormGroup, FormControl, FormArray, NgForm } from '@angular/forms'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: 'src/app.component.html' }) export class AppComponent implements OnInit { private myForm: FormGroup; constructor() { } ngOnInit() { this.myForm = new FormGroup({ 'name': new FormControl(), 'birthYear': new FormControl(), 'location': new FormGroup({ 'country': new FormControl(), 'city': new FormControl() }), 'phoneNumbers': new FormArray([new FormControl('')]) }); } printMyForm() { console.log(this.myForm); } register(myForm: NgForm) { console.log('Registration successful.'); console.log(myForm.value); } }

Les méthodes printForm et register sont les mêmes que dans les exemples précédents et seront utilisées dans les étapes suivantes. Les types de clé utilisés ici sont FormGroup, FormControl et FormArray. Ces trois types sont tout ce dont nous avons besoin pour créer un FormGroup valide. Le FormGroup est simple ; il s'agit d'un simple conteneur de contrôles. Le FormControl est également facile ; il s'agit de n'importe quel contrôle (par exemple, une entrée). Et enfin, le FormArray est la pièce du puzzle qui nous manquait dans l'approche basée sur les modèles. Le FormArray permet de maintenir un groupe de contrôles sans spécifier de clé concrète pour chaque contrôle, essentiellement un tableau de contrôles (cela semble être la chose parfaite pour les numéros de téléphone, non ?).

Lors de la construction de l'un de ces trois types, rappelez-vous cette règle de 3. Le constructeur de chaque type reçoit trois arguments : value , validator ou liste de validateurs et validator async ou liste de validateurs async, définis dans le code :

 constructor(value: any, validator?: ValidatorFn | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[]);

Pour FormGroup, la value est un objet où chaque clé représente le nom d'un contrôle et la valeur est le contrôle lui-même.

Pour FormArray, la value est un tableau de contrôles.

Pour FormControl, la value est la valeur initiale ou l'état initial (objet contenant une value et une propriété disabled ) du contrôle.

Nous avons créé l'objet FormGroup, mais le modèle n'est toujours pas conscient de cet objet. La liaison entre le FormGroup dans le composant et le modèle se fait avec quatre directives : formGroup , formControlName , formGroupName et formArrayName , utilisées comme ceci :

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)"> <div> <label>Name</label> <input type="text" name="name" formControlName="name"> </div> <div> <label>Birth Year</label> <input type="text" name="birthYear" formControlName="birthYear"> </div> <div formGroupName="location"> <h3>Location</h3> <div> <label>Country</label> <input type="text" name="country" formControlName="country"> </div> <div> <label>City</label> <input type="text" name="city" formControlName="city"> </div> </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> <div *ngFor="let phoneNumberControl of myForm.controls.phoneNumbers.controls; let i=index;"> <label>Phone number {{i + 1}}</label> <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i"> <button type="button" (click)="remove(i)">remove</button> </div> <button type="button" (click)="add()">Add phone number</button> </div> <pre>{{myForm.value | json}}</pre> <button type="submit">Register</button> <button type="button" (click)="printMyForm()">Print to console</button> </form>

Maintenant que nous avons le FormArray, vous pouvez voir que nous pouvons utiliser cette structure pour afficher tous les numéros de téléphone.

Et maintenant, pour ajouter la prise en charge de l'ajout et de la suppression de numéros de téléphone (dans le composant) :

 remove(i: number) { (<FormArray>this.myForm.get('phoneNumbers')).removeAt(i); } add() { (<FormArray>this.myForm.get('phoneNumbers')).push(new FormControl('')); }

Maintenant, nous avons une forme réactive Angular 4 entièrement fonctionnelle. Notez la différence avec les formulaires basés sur des modèles où le FormGroup a été "créé dans le modèle" (en scannant la structure du modèle) et transmis au composant, dans les formulaires réactifs, c'est l'inverse, le FormGroup complet est créé dans le composant, puis "passé au modèle" et lié aux contrôles correspondants. Mais, encore une fois, nous avons le même problème avec la validation, un problème qui sera résolu dans la section suivante.

Validation

En ce qui concerne la validation, les formulaires réactifs sont beaucoup plus flexibles que les formulaires basés sur des modèles. Sans modifications supplémentaires, nous pouvons réutiliser les mêmes validateurs qui ont été implémentés précédemment (pour le template-driven). Ainsi, en ajoutant les directives du validateur, nous pouvons activer la même validation :

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)" countryCity telephoneNumbers novalidate> <input type="text" name="name" formControlName="name" required uniqueName> <show-errors [control]="myForm.controls.name"></show-errors> .. <input type="text" name="birthYear" formControlName="birthYear" required birthYear> <show-errors [control]="myForm.controls.birthYear"></show-errors> .. <div formGroupName="location"> .. <input type="text" name="country" formControlName="country" required> <show-errors [control]="myForm.controls.location.controls.country"></show-errors> .. <input type="text" name="city" formControlName="city"> .. </div> <div formArrayName="phoneNumbers"> <h3>Phone numbers</h3> .. <input type="text" name="phoneNumber[{{phoneId}}]" [formControlName]="i" required telephoneNumber> <show-errors [control]="phoneNumberControl"></show-errors> .. </div> .. </form>

Gardez à l'esprit que nous n'avons plus la directive NgModel à transmettre au composant ShowErrors, mais que le FormGroup complet est déjà construit et que nous pouvons transmettre le bon AbstractControl pour récupérer les erreurs.

Voici un Plunker complet avec ce type de validation pour les formulaires réactifs.

Mais ce ne serait pas amusant si nous réutilisions simplement les validateurs, n'est-ce pas ? Nous verrons comment spécifier les validateurs lors de la création du groupe de formulaires.

Vous souvenez-vous de la règle de la « règle des 3 s » que nous avons mentionnée à propos du constructeur pour FormGroup, FormControl et FormArray ? Oui, nous avons dit que le constructeur peut recevoir des fonctions de validation. Alors, essayons cette approche.

Tout d'abord, nous devons extraire les fonctions de validate de tous les validateurs dans une classe les exposant en tant que méthodes statiques :

 import { FormArray, FormControl, FormGroup, ValidationErrors } from '@angular/forms'; export class CustomValidators { static birthYear(c: FormControl): ValidationErrors { const numValue = Number(c.value); const currentYear = new Date().getFullYear(); const minYear = currentYear - 85; const maxYear = currentYear - 18; const isValid = !isNaN(numValue) && numValue >= minYear && numValue <= maxYear; const message = { 'years': { 'message': 'The year must be a valid number between ' + minYear + ' and ' + maxYear } }; return isValid ? null : message; } static countryCity(form: FormGroup): ValidationErrors { const countryControl = form.get('location.country'); const cityControl = form.get('location.city'); if (countryControl != null && cityControl != null) { const country = countryControl.value; const city = cityControl.value; let error = null; if (country === 'France' && city !== 'Paris') { error = 'If the country is France, the city must be Paris'; } const message = { 'countryCity': { 'message': error } }; return error ? message : null; } } static uniqueName(c: FormControl): Promise<ValidationErrors> { const message = { 'uniqueName': { 'message': 'The name is not unique' } }; return new Promise(resolve => { setTimeout(() => { resolve(c.value === 'Existing' ? message : null); }, 1000); }); } static telephoneNumber(c: FormControl): ValidationErrors { const isValidPhoneNumber = /^\d{3,3}-\d{3,3}-\d{3,3}$/.test(c.value); const message = { 'telephoneNumber': { 'message': 'The phone number must be valid (XXX-XXX-XXX, where X is a digit)' } }; return isValidPhoneNumber ? null : message; } static telephoneNumbers(form: FormGroup): ValidationErrors { const message = { 'telephoneNumbers': { 'message': 'At least one telephone number must be entered' } }; const phoneNumbers = <FormArray>form.get('phoneNumbers'); const hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers.controls).length > 0; return hasPhoneNumbers ? null : message; } }

Now we can change the creation of 'myForm' to:

 this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()]) }, Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) );

Voir? The rule of “3s,” when defining a FormControl, multiple validators can be declared in an array, and if we want to add multiple validators to a FormGroup they must be “merged” using Validators.compose (also Validators.composeAsync is available). And, that's it, validation should be working completely. There's a Plunker for this example as well.

This goes out to everybody that hates the “new” word. For working with the reactive forms, there's a shortcut provided—a builder, to be more precise. The FormBuilder allows creating the complete FormGroup by using the “builder pattern.” And that can be done by changing the FormGroup construction like this:

 constructor(private fb: FormBuilder) { } ngOnInit() { this.myForm = this.fb.group({ 'name': ['', Validators.required, CustomValidators.uniqueName], 'birthYear': ['', [Validators.required, CustomValidators.birthYear]], 'location': this.fb.group({ 'country': ['', Validators.required], 'city': '' }), 'phoneNumbers': this.fb.array([this.buildPhoneNumberComponent()]) }, { validator: Validators.compose([CustomValidators.countryCity, CustomValidators.telephoneNumbers]) } ); }

Not a very big improvement from the instantiation with “new,” but there it is. And, don't worry, there's a Plunker for this also.

In this second section, we had a look at reactive forms in Angular 4. As you may notice, it is a completely new approach towards adding support for forms. Even though it seems verbose, this approach gives the developer total control over the underlying structure that enables forms in Angular 4. Also, since the reactive forms are created manually in the component, they are exposed and provide an easy way to be tested and controlled, while this was not the case with the template-driven forms.

Nesting Forms

Nesting forms is in some cases useful and a required feature, mainly when the state (eg, validity) of a sub-group of controls needs to determined. Think about a tree of components; we might be interested in the validity of a certain component in the middle of that hierarchy. That would be really hard to achieve if we had a single form at the root component. But, oh boy, it is a sensitive manner on a couple of levels. First, nesting real HTML forms, according to the HTML specification, is not allowed. We might try to nest <form> elements. In some browsers it might actually work, but we cannot be sure that it will work on all browsers, since it is not in the HTML spec. In AngularJS, the way to work around this limitation was to use the ngForm directive, which offered the AngularJS form functionalities (just grouping of the controls, not all form capabilities like posting to the server) but could be placed on any element. Also, in AngularJS, nesting of forms (when I say forms, I mean NgForm) was available out of the box. Just by declaring a tree of couple of elements with the ngForm directive, the state of each form was propagated upwards to the root element.

In the next section, we will have a look at a couple options on how to nest forms. I like to point out that we can differentiate two types of nesting: within the same component and across different components.

Nesting within the Same Component

If you take a look at the example that we implemented with the template-driven and the reactive approach, you will notice that we have two inner containers of controls, the “location” and the “phone numbers.” To create that container, to store the values in a separate property object, we used the NgModelGroup, FormGroupName, and the FormArrayName directives. If you have a good look at the definition of each directive, you may notice that each one of them extends the ControlContainer class (directly or indirectly). Well, what do you know, it turns out this is enough to provide the functionality that we require, wrapping up the state of all inner controls and propagating that state to the parent.

For the template-driven form, we need to do the following changes:

 <form #myForm="ngForm" (ngSubmit)="register(myForm)" novalidate> .. <div ngModelGroup="location" #location="ngModelGroup" countryCity> .. <show-errors [control]="location"></show-errors> </div> <div ngModelGroup="phoneNumbers" #phoneNumbers="ngModelGroup" telephoneNumbers> .. <show-errors [control]="phoneNumbers"></show-errors> </div> </form>

We added the ShowErrors component to each group, to show the errors directly associated with that group only. Since we moved the countryCity and telephoneNumbers validators to a different level, we also need to update them appropriately:

 // country-city-validator.directive.ts let countryControl = form.get('country'); let cityControl = form.get('city');

And telephone-numbers-validator.directive.ts to:

 let phoneNumbers = form.controls; let hasPhoneNumbers = phoneNumbers && Object.keys(phoneNumbers).length > 0;

You can try the full example with template-driven forms in this Plunker.

And for the reactive forms, we will need some similar changes:

 <form [formGroup]="myForm" (ngSubmit)="register(myForm)" novalidate> .. <div formGroupName="location"> .. <show-errors [control]="myForm.controls.location"></show-errors> </div> <div formArrayName="phoneNumbers"> .. <show-errors [control]="myForm.controls.phoneNumbers"></show-errors> </div> .. </form>

The same changes from country-city-validator.directive.ts and telephone-numbers-validator.directive.ts are required for the countryCity and telephoneNumbers validators in CustomValidators to properly locate the controls.

And lastly, we need to modify the construction of the FormGroup to:

 this.myForm = new FormGroup({ 'name': new FormControl('', Validators.required, CustomValidators.uniqueName), 'birthYear': new FormControl('', [Validators.required, CustomValidators.birthYear]), 'location': new FormGroup({ 'country': new FormControl('', Validators.required), 'city': new FormControl() }, CustomValidators.countryCity), 'phoneNumbers': new FormArray([this.buildPhoneNumberComponent()], CustomValidators.telephoneNumbers) });

And there you have it—we've improved the validation for the reactive forms as well and as expected, the Plunker for this example.

Nesting across Different Components

It may come as a shock to all AngularJS developers, but in Angular 4, nesting of forms across different component doesn't work out of the box. I'm going to be straight honest with you; my opinion is that nesting is not supported for a reason (probably not because the Angular 4 team just forgot about it). Angular4's main enforced principle is a one-way data flow, top to bottom through the tree of components. The whole framework was designed like that, where the vital operation, the change detection, is executed in the same manner, top to bottom. If we follow this principle completely, we should have no issues, and all changes should be resolved within one full detection cycle. That's the idea, at least. In order to check that one-way data flow is implemented correctly, the nice guys in the Angular 4 team implemented a feature that after each change detection cycle, while in development mode, an additional round of change detection is triggered to check that no binding was changed as a result of reverse data propagation. What this means, let's think about a tree of components (C1, C2, C3, C4) as in Fig. 1, the change detection starts at the C1 component, continues at the C2 component and ends in the C3 component.

A tree of nested components holding a form.

If we have some method in C3 with a side effect that changes some binding in C1, that means that we are pushing data upwards, but the change detection for C1 already passed. When working in dev mode, the second round kicks in and notices a change in C1 that came as a result of a method execution in some child component. Then you are in trouble and you'll probably see the “Expression has changed after it was checked” exception. You could just turn off the development mode and there will be no exception, but the problem will not be solved; plus, how would you sleep at night, just sweeping all your problems under the rug like that?

Une fois que vous savez cela, réfléchissez à ce que nous faisons si nous agrégeons l'état des formulaires. C'est vrai, les données sont poussées vers le haut de l'arborescence des composants. Même lorsque vous travaillez avec des formulaires à un seul niveau, l'intégration des contrôles de formulaire ( ngModel ) et le formulaire lui-même n'est pas très agréable. Ils déclenchent un cycle de détection de changement supplémentaire lors de l'enregistrement ou de la mise à jour de la valeur d'un contrôle (cela se fait en utilisant une promesse résolue, mais gardez-la secrète). Pourquoi un tour supplémentaire est-il nécessaire ? Eh bien, pour la même raison, les données circulent vers le haut, du contrôle au formulaire. Mais, peut-être parfois, l'imbrication de formulaires sur plusieurs composants est une fonctionnalité requise et nous devons penser à une solution pour répondre à cette exigence.

Avec ce que nous savons jusqu'à présent, la première idée qui vient à l'esprit est d'utiliser des formulaires réactifs, de créer l'arbre de formulaire complet dans un composant racine, puis de transmettre les formulaires enfants aux composants enfants en tant qu'entrées. De cette façon, vous avez étroitement couplé le parent aux composants enfants et encombré la logique métier du composant racine avec la gestion de la création de tous les formulaires enfants. Allez, nous sommes des professionnels, je suis sûr que nous pouvons trouver un moyen de créer des composants totalement isolés avec des formulaires et fournir un moyen au formulaire de simplement propager l'état à celui qui est le parent.

Tout cela étant dit, voici une directive qui permet d'imbriquer les formulaires Angular 4 (implémentée car elle était nécessaire pour un projet):

 import { OnInit, OnDestroy, Directive, SkipSelf, Optional, Attribute, Injector, Input } from '@angular/core'; import { NgForm, FormArray, FormGroup, AbstractControl } from '@angular/forms'; const resolvedPromise = Promise.resolve(null); @Directive({ selector: '[nestableForm]' }) export class NestableFormDirective implements OnInit, OnDestroy { private static readonly FORM_ARRAY_NAME = 'CHILD_FORMS'; private currentForm: FormGroup; @Input() private formGroup: FormGroup; constructor(@SkipSelf() @Optional() private parentForm: NestableFormDirective, private injector: Injector, @Attribute('rootNestableForm') private isRoot) { } ngOnInit() { if (!this.currentForm) { // NOTE: at this point both NgForm and ReactiveFrom should be available this.executePostponed(() => this.resolveAndRegister()); } } ngOnDestroy() { this.executePostponed(() => this.parentForm.removeControl(this.currentForm)); } public registerNestedForm(control: AbstractControl): void { // NOTE: prevent circular reference (adding to itself) if (control === this.currentForm) { throw new Error('Trying to add itself! Nestable form can be added only on parent "NgForm" or "FormGroup".'); } (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)).push(control); } public removeControl(control: AbstractControl): void { const array = (<FormArray>this.currentForm.get(NestableFormDirective.FORM_ARRAY_NAME)); const idx = array.controls.indexOf(control); array.removeAt(idx); } private resolveAndRegister(): void { this.currentForm = this.resolveCurrentForm(); this.currentForm.addControl(NestableFormDirective.FORM_ARRAY_NAME, new FormArray([])); this.registerToParent(); } private resolveCurrentForm(): FormGroup { // NOTE: template-driven or model-driven => determined by the formGroup input return this.formGroup ? this.formGroup : this.injector.get(NgForm).control; } private registerToParent(): void { if (this.parentForm != null && !this.isRoot) { this.parentForm.registerNestedForm(this.currentForm); } } private executePostponed(callback: () => void): void { resolvedPromise.then(() => callback()); } }

L'exemple dans le GIF suivant montre un composant main contenant form-1 et, à l'intérieur de ce formulaire, il y a un autre composant imbriqué, component-2 . component-2 contient form-2 , qui a imbriqué form-2.1 , form-2.2 , et un composant ( component-3 ) qui contient une arborescence d'un formulaire réactif et un composant ( component-4 ) qui contient un formulaire qui est isolé de toutes les autres formes. Assez brouillon, je sais, mais je voulais faire un scénario assez complexe pour montrer la fonctionnalité de cette directive.

Un cas complexe de validation de forme angulaire avec plusieurs composants

L'exemple est implémenté dans ce Plunker.

Les fonctionnalités qu'il propose sont :

  • Active l'imbrication en ajoutant la directive nestableForm aux éléments : form, ngForm, [ngForm], [formGroup]

  • Fonctionne avec des formulaires basés sur des modèles et réactifs

  • Permet de créer une arborescence de formulaires couvrant plusieurs composants

  • Isole une sous-arborescence de formulaires avec rootNestableForm=”true” (il ne s'enregistrera pas dans le parent nestableForm)

Cette directive permet à un formulaire dans un composant enfant de s'enregistrer auprès du premier parent nestableForm, que le formulaire parent soit déclaré ou non dans le même composant. Nous allons entrer dans les détails de la mise en œuvre.

Tout d'abord, regardons le constructeur. Le premier argument est :

 @SkipSelf() @Optional() private parentForm: NestableFormDirective

Cela recherche le premier parent NestableFormDirective. @SkipSelf, pour ne pas correspondre à lui-même, et @Optional car il peut ne pas trouver de parent, dans le cas du formulaire racine. Nous avons maintenant une référence au formulaire imbriqué parent.

Le deuxième argument est :

 private injector: Injector

L'injecteur permet de récupérer le provider FormGroup actuel (modèle ou réactif).

Et le dernier argument est :

 @Attribute('rootNestableForm') private isRoot

pour obtenir la valeur qui détermine si ce formulaire est isolé de l'arborescence des formulaires.

Ensuite, sur ngInit en tant qu'action différée (rappelez-vous le flux de données inverse ?), le FormGroup actuel est résolu, un nouveau contrôle FormArray nommé CHILD_FORMS est enregistré dans ce FormGroup (où les formulaires enfants seront enregistrés) et comme dernière action, le le FormGroup actuel est enregistré en tant qu'enfant du formulaire imbriqué parent.

L'action ngOnDestroy est exécutée lorsque le formulaire est détruit. Lors de la destruction, encore une fois en tant qu'action différée, le formulaire actuel est supprimé du parent (désenregistrement).

La directive pour les formulaires imbriqués peut être davantage personnalisée pour un besoin spécifique - peut-être supprimer la prise en charge des formulaires réactifs, enregistrer chaque formulaire enfant sous un nom spécifique (pas dans un tableau CHILD_FORMS), etc. Cette implémentation de la directive nestableForm répondait aux exigences du projet et est présentée ici comme telle. Il couvre certains cas de base comme l'ajout d'un nouveau formulaire ou la suppression dynamique d'un formulaire existant (*ngIf) et la propagation de l'état du formulaire au parent. Cela se résume essentiellement à des opérations qui peuvent être résolues dans un cycle de détection de changement (avec report ou non).

Si vous voulez un scénario plus avancé comme l'ajout d'une validation conditionnelle à une entrée (par exemple [required]=”someCondition”) qui nécessiterait 2 tours de détection de changement, cela ne fonctionnera pas à cause de la règle “one-detection-cycle-resolution” imposé par Angular 4.

Quoi qu'il en soit, si vous envisagez d'utiliser cette directive ou d'implémenter une autre solution, soyez très prudent en ce qui concerne les éléments mentionnés concernant la détection des modifications. À ce stade, voici comment Angular 4 est implémenté. Cela pourrait changer à l'avenir, nous ne pouvons pas le savoir. La configuration actuelle et la restriction appliquée dans Angular 4 mentionnées dans cet article peuvent être un inconvénient ou un avantage. Cela reste à voir.

Formulaires simplifiés avec Angular 4

Comme vous pouvez le voir, l'équipe Angular a fait un très bon travail en fournissant de nombreuses fonctionnalités liées aux formulaires. J'espère que cet article servira de guide complet pour travailler avec les différents types de formulaires dans Angular 4, donnant également un aperçu de certains concepts plus avancés comme l'imbrication des formulaires et le processus de détection des changements.

Malgré tous les différents messages liés aux formulaires Angular 4 (ou à tout autre sujet Angular 4 d'ailleurs), à mon avis, le meilleur point de départ est la documentation officielle Angular 4. De plus, les gars d'Angular ont une belle documentation dans leur code. Plusieurs fois, j'ai trouvé une solution simplement en regardant leur code source et la documentation là-bas, pas de recherche sur Google ou quoi que ce soit. À propos de l'imbrication des formulaires, abordée dans la dernière section, je pense que tout développeur AngularJS qui commence à apprendre Angular 4 tombera sur ce problème à un moment donné, ce qui m'a en quelque sorte inspiré pour écrire cet article.

Comme nous l'avons également vu, il existe deux types de formulaires, et il n'y a pas de règle stricte interdisant de les utiliser ensemble. C'est bien de garder la base de code propre et cohérente, mais parfois, quelque chose peut être fait plus facilement avec des formulaires basés sur des modèles et, parfois, c'est l'inverse. Donc, si les tailles de bundle légèrement plus grandes ne vous dérangent pas, je vous suggère d'utiliser ce que vous considérez le plus approprié au cas par cas. Ne les mélangez pas dans le même composant, car cela conduirait probablement à une certaine confusion.

Plunkers utilisés dans ce message

  • Formulaires basés sur des modèles

  • Formulaires réactifs, validateurs de modèles

  • Formulaires réactifs, validateurs de code

  • Formulaires réactifs, générateur de formulaires

  • Formulaires basés sur des modèles, imbriqués dans le même composant

  • Formulaires réactifs, imbriqués dans le même composant

  • Formulaires imbriqués dans l'arborescence des composants

En relation: Validation de formulaire Smart Node.js