Formulaires
Formulaires pilotés par template
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-template-form',
standalone: true,
imports: [FormsModule],
template: `
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input
name="username"
[(ngModel)]="user.username"
required
#username="ngModel">
<div *ngIf="username.invalid && username.touched">
Username required
</div>
<input
type="email"
name="email"
[(ngModel)]="user.email"
required
email>
<button [disabled]="userForm.invalid">Submit</button>
</form>
`
})
export class TemplateFormComponent {
user = { username: '', email: '' };
onSubmit(form: any) {
console.log(form.value);
}
}
Formulaires réactifs
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="username">
<div *ngIf="username?.invalid && username?.touched">
Username required (min 3 chars)
</div>
<input formControlName="email" type="email">
<div *ngIf="email?.errors?.['required']">Email required</div>
<div *ngIf="email?.errors?.['email']">Invalid email</div>
<button [disabled]="userForm.invalid">Submit</button>
</form>
`
})
export class ReactiveFormComponent {
fb = inject(FormBuilder);
userForm = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]]
});
get username() { return this.userForm.get('username'); }
get email() { return this.userForm.get('email'); }
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}
FormControl & FormGroup
import { FormControl, FormGroup } from '@angular/forms';
// Individual control
nameControl = new FormControl('', Validators.required);
// Form group
userForm = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email]),
age: new FormControl(0, [Validators.min(18), Validators.max(99)])
});
// Get values
this.userForm.value; // { name: '...', email: '...', age: 0 }
this.userForm.get('name')?.value;
// Set values
this.userForm.setValue({ name: 'John', email: 'john@test.com', age: 25 });
this.userForm.patchValue({ name: 'Jane' }); // Partial update
// Validation
this.userForm.valid;
this.userForm.invalid;
this.userForm.errors;
FormArray
import { FormArray, FormControl } from '@angular/forms';
@Component({
template: `
<form [formGroup]="form">
<div formArrayName="skills">
<div *ngFor="let skill of skills.controls; let i = index">
<input [formControlName]="i">
<button (click)="removeSkill(i)">Remove</button>
</div>
</div>
<button (click)="addSkill()">Add Skill</button>
</form>
`
})
export class FormArrayComponent {
form = new FormGroup({
skills: new FormArray([
new FormControl('')
])
});
get skills() {
return this.form.get('skills') as FormArray;
}
addSkill() {
this.skills.push(new FormControl(''));
}
removeSkill(index: number) {
this.skills.removeAt(index);
}
}
Validation
Validateurs personnalisés
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// Validateur synchrone
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
// Usage
username: ['', [Validators.required, forbiddenNameValidator(/admin/)]]
Validateurs asynchrones
// Validateur asynchrone
export function uniqueEmailValidator(userService: UserService): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return userService.checkEmail(control.value).pipe(
map(exists => exists ? { emailExists: true } : null)
);
};
}
Contrôles de formulaire personnalisés
Implémenter ControlValueAccessor pour créer des contrôles de formulaire réutilisables :
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-toggle-switch',
template: `<input type="checkbox" [checked]="value" (change)="onChange($event.target.checked)">`,
providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: ToggleSwitchComponent, multi: true }]
})
export class ToggleSwitchComponent implements ControlValueAccessor {
value = false;
onChange = (_: boolean) => {};
onTouched = () => {};
writeValue(val: boolean) { this.value = val; }
registerOnChange(fn: (_: boolean) => void) { this.onChange = fn; }
registerOnTouched(fn: () => void) { this.onTouched = fn; }
}
Value Changes
// Surveiller les changements du formulaire
this.userForm.valueChanges.subscribe(value => {
console.log('Form changed:', value);
});
// Watch specific field
this.userForm.get('email')?.valueChanges.subscribe(email => {
console.log('Email changed:', email);
});
// Debounce
this.userForm.get('search')?.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged()
).subscribe(searchTerm => {
this.search(searchTerm);
});
Bonnes Pratiques
- Utiliser Reactive Forms pour les formulaires complexes
- Utiliser Template-driven pour les formulaires simples
- Toujours valider côté client et serveur
- Utiliser FormBuilder pour un code plus propre
- Se désabonner de
valueChanges(ou utiliser le pipeasync)