Aller au contenu principal

Reactive Forms

Créer un nouveau projet

  • Créer un nouveau projet Angular en utilisant le mode --no-standalone
ng new --no-standalone ngReactiveForms
  • Les autres options ne sont pas importantes.

Création d'un premier formulaire

Validation dynamique

  • On peut afficher des messages d'erreurs directement sur les champs

image

Configuration

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './authentification/login/login.component';
import { RegisterComponent } from './authentification/register/register.component';
import { MatInputModule } from '@angular/material/input';
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
MatInputModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
  • ReactiveFormsModule nous permet de faire de la validation dynamique sur les champs d'un formulaire
  • MatInputModule et Material Nous permet d'afficher facilement les messages d'erreurs sous les champs du formulaire
attention

Il faut installer Material pour utiliser MatInput

ng add @angular/material

Injecter Formbuilder

Il faut injecter formBuilder dans le component où l'on veut ajouter notre formulaire.

@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
constructor(private fb: FormBuilder) { }
//...
}

Utiliser le FormBuilder

  • Créer un groupe de validation à partir du FormBuilder
  • Chaque champ du formulaire que l'on doit valider peut avoir un ou plusieurs validateurs
form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
name: ['',[Validators.required]],
});

Les Validators

  • Il existe plusieurs Validators par défaut
  • Vous retrouverez les même Validations que l'on peut mettre sur un modèle en C#

image

Créer un Validator

On peut créer des "custom" validator et les affecter à un champ du groupe

form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
name: ['',[Validators.required, this.myCustomValidator]],
});

Exemple sur un control

myCustomValidator(control: AbstractControl): ValidationErrors | null {
// On récupère la valeur du champs texte
const email = control.value;
// On regarde si le champs est remplis avant de faire la validation
if (!email) {
return null;
}
// On fait notre validation
let formValid = email.includes('@gmail.com');
// Si le formulaire est invalide on retourne l'erreur
// Si le formulaire est valide on retourne null
return !formValid?{gmailValidator:true}:null;
}

Validator sur plusieurs champ

On peut également utiliser un Validator custom sur le formulaire pour faire une validations sur plusieurs champs (ex. Mot de passe et confirmation)

form = this.fb.group({
email: ['', [Validators.required, Validators.email]],
name: ['',[Validators.required]],
}, { validators: this.myCustomValidator });

Exemple sur un form

myCustomValidator(form: AbstractControl): ValidationErrors | null {
// On récupère les valeurs de nos champs textes
const email = form.get('email')?.value;
const name = form.get('name')?.value;
// On regarde si les champs sont remplis avant de faire la validation
if (!email || !name) {
return null;
}
// On fait notre validation
let formValid = email.includes(name);

// Si le formulaire est invalide on retourne l'erreur
// Si le formulaire est valide on retourne null
return !formValid?{nameInEmail:true}:null;
}

form.valueChanges

  • Pour récupérer les données du formulaire, nous utiliserons un Observable sur l'évènement valueChanges
  • Il faudra aussi avoir créer un classe (ou une interface) du même type que le formulaire
  • Il faudra finalement créer une variable du type du formulaire
// interface qui décris le type du formulaire
interface Data {
email?: string | null ;
name?: string | null ;
}
export class RegisterComponent implements OnInit{
// Le component contient une variable du même type que les champs du formulaire
formData?: Data;
ngOnInit(): void {
// À chaque fois que les valeurs changent, notre propriétés formData sera mise à jour
this.form.valueChanges.subscribe(() => {
this.formData = this.form.value;
});
}
//...
}

setErrors

C'est également possible de mettre une erreur directement sur un control à l'intérieur d'une validation

if(error){
form.get('email')?.setErrors({nameInEmail:true});
}
else{
form.get('email')?.setErrors(null);
}
danger

Il faut être prudent avec l'utilisation de setErrors, surtout setErrors(null), car elle écrase les erreurs qui existent déjà!

Version complète

@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {

form:FormGroup<any>;
// Le component contient une variable du même type que les champs du formulaire
formData?: Data;

constructor(private fb: FormBuilder) {
this.form = this.fb.group({
email: ['', [Validators.required, Validators.email, this.gmailValidator]],
name: ['',[Validators.required]],
}, { validators: this.myCustomValidator });

// À chaque fois que les valeurs changent, notre propriétés formData sera mise à jour
this.form.valueChanges.subscribe(() => {
this.formData = this.form.value;
});
}

gmailValidator(control: AbstractControl): ValidationErrors | null {
// On récupère la valeur du champs texte
const email = control.value;
// On regarde si le champs est remplis avant de faire la validation
if (!email) {
return null;
}
// On fait notre validation
let formValid = email.includes('@gmail.com');
// On mets les champs concernés en erreur
// Si le formulaire est invalide on retourne l'erreur
// Si le formulaire est valide on retourne null
return !formValid?{gmailValidator:true}:null;
}
myCustomValidator(form: AbstractControl): ValidationErrors | null {
// On récupère les valeurs de nos champs textes
const email = form.get('email')?.value;
const name = form.get('name')?.value;
// On regarde si les champs sont remplis avant de faire la validation
if (!email || !name) {
return null;
}
// On fait notre validation
let formValid = email.includes(name);
// Si le formulaire est invalide on retourne l'erreur
// Si le formulaire est valide on retourne null
return !formValid?{nameInEmail:true}:null;
}
}
// interface qui décris le type du formulaire
interface Data {
email?: string | null ;
name?: string | null ;
}

L'utilisation de ReactiveForms dans la vue

  • Ajouter le groupe de validation au formulaire hmtl
<form [formGroup]="form">
...
</form>
  • Ajouter les champs textes
<mat-form-field style="width: 100%">
<input matInput type="text" placeholder="Votre nom" formControlName="name" name="name">
<mat-error *ngIf="form.get('name')?.hasError('required')">
Votre nom est <strong>requis</strong>
</mat-error>
<mat-error *ngIf="form.hasError('nameInEmail')">
Le nom doit être dans l'adresse courriel
</mat-error>
</mat-form-field>

formControlName="name"

  • On lie le champ texte au contrôle "name" dans le groupe du formulaire (groupe de validation)

form.get('name')?.hasError('required')

  • On vérifie s'il y a une erreur de type required sur le champ

form.hasError('nameInEmail')

  • On regarde s'il y a notre erreur "custom" sur l'ensemble du formulaire

mat-error

  • On affiche un message d'erreur sous le champ texte
 <mat-error *ngIf="form.hasError('nameInEmail')">
Le nom doit être dans l'adresse courriel
</mat-error>

Version finale

<div style="width: 100%;height: 100%; display: flex; justify-content: center; align-items: center; flex-direction: column">
<mat-card class="artist-card" style="margin: 16px; padding: 16px;">
<form [formGroup]="form">
<mat-form-field style="width: 100%">
<input matInput type="email" placeholder="Courriel" formControlName="email" name="email">
<mat-error *ngIf="form.get('email')?.errors?.['email'] && !form.get('email')?.hasError('required')">
Entrer une adresse courriel valide
</mat-error>
<mat-error *ngIf="form.get('email')?.hasError('gmail') && !loginForm.get('email')?.errors?.['email']">
Le courriel doit venir de <strong>Google</strong>
</mat-error>
<mat-error *ngIf="form.get('email')?.hasError('required')">
Le courriel est <strong>requis</strong>
</mat-error>
</mat-form-field>
<mat-form-field style="width: 100%">
<input matInput type="text" placeholder="Votre nom" formControlName="name" name="name">
<mat-error *ngIf="form.get('name')?.hasError('required')">
Votre nom est <strong>requis</strong>
</mat-error>
<mat-error *ngIf="form.hasError('nameInEmail')">
Le nom doit être dans l'adresse courriel
</mat-error>
</mat-form-field>
<button mat-raised-button color="primary" [disabled]="!form.valid">Enregistrer</button>
</form>
</mat-card>
</div>
danger

Un <mat-error> s'affiche uniquement si il est sur un control (ou formulaire) avec au moins une erreur. Si ce n'est pas le cas, il ne s'affiche pas, même si le *ngIf est vrai!