Angular Reactive Forms
Reactive forms provide a model-driven approach to handling form inputs whose values change over time. They are more robust than template-driven forms, especially for complex scenarios.
Reactive Forms vs Template-Driven Forms
- Reactive Forms: More explicit, created in component class, immutable, easier to test
- Template-Driven: Less explicit, created by directives, mutable, familiar to AngularJS users
Setting Up Reactive Forms
First, import ReactiveFormsModule in your module:
// app.module.ts
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
ReactiveFormsModule
]
})
export class AppModule { }
Form Controls
The basic building block of reactive forms is FormControl:
// In your component
import { FormControl } from '@angular/forms';
email = new FormControl('');
// Set value
email.setValue('user@example.com');
// Get value
console.log(email.value);
// Listen to value changes
email.valueChanges.subscribe(value => {
console.log('Email changed:', value);
});
Form Groups
Group multiple controls together:
import { FormGroup, FormControl } from '@angular/forms';
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
email: new FormControl('')
});
// Set values
profileForm.setValue({
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com'
});
// Get values
console.log(profileForm.value);
Form Builder
Simplify form creation with FormBuilder:
import { FormBuilder } from '@angular/forms';
constructor(private fb: FormBuilder) {}
profileForm = this.fb.group({
firstName: [''],
lastName: [''],
email: ['']
});
Template Binding
Bind form controls to template elements:
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>
Form Validation
Add validation to your forms:
import { Validators } from '@angular/forms';
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]]
});
Display validation messages:
<div *ngIf="profileForm.get('email').invalid &&
(profileForm.get('email').dirty || profileForm.get('email').touched)">
<div *ngIf="profileForm.get('email').errors.required">
Email is required.
</div>
<div *ngIf="profileForm.get('email').errors.email">
Please enter a valid email.
</div>
</div>
Dynamic Forms
Create forms with dynamic controls:
profileForm = this.fb.group({
firstName: [''],
lastName: [''],
addresses: this.fb.array([
this.fb.control('')
])
});
get addresses() {
return this.profileForm.get('addresses') as FormArray;
}
addAddress() {
this.addresses.push(this.fb.control(''));
}
<div formArrayName="addresses">
<div *ngFor="let address of addresses.controls; let i=index">
<input [formControlName]="i">
</div>
</div>
<button (click)="addAddress()">Add Address</button>
Custom Validators
Create your own validation functions:
function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {forbiddenName: {value: control.value}} : null;
};
}
// Usage
profileForm = this.fb.group({
firstName: ['', [
Validators.required,
forbiddenNameValidator(/admin/i)
]]
});
Form Submission
Handle form submission:
onSubmit() {
if (this.profileForm.valid) {
console.log('Form data:', this.profileForm.value);
// Send data to server
} else {
// Mark all fields as touched to show errors
this.markFormGroupTouched(this.profileForm);
}
}
markFormGroupTouched(formGroup: FormGroup) {
Object.values(formGroup.controls).forEach(control => {
control.markAsTouched();
if (control instanceof FormGroup) {
this.markFormGroupTouched(control);
}
});
}
Value Changes and Status Changes
React to form changes:
// Listen to value changes
this.profileForm.valueChanges.subscribe(value => {
console.log('Form value changed:', value);
});
// Listen to status changes
this.profileForm.statusChanges.subscribe(status => {
console.log('Form status:', status);
});
Next: HTTP Client