Reactive Forms
Building forms in the component class with FormGroup and FormControl for more predictable, testable form logic.
2 phút đọc
Where template-driven forms define structure in the template, reactive forms define it in the component class as an explicit object — a FormGroup made of FormControls — that the template simply binds to.
Building the form model
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-signup-form',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<input formControlName="email" />
<input formControlName="password" type="password" />
<button type="submit" [disabled]="signupForm.invalid">Sign up</button>
</form>
`,
})
export class SignupFormComponent {
signupForm = new FormGroup({
email: new FormControl('', [Validators.required, Validators.email]),
password: new FormControl('', [Validators.required, Validators.minLength(8)]),
});
onSubmit(): void {
console.log(this.signupForm.value);
}
}The entire form — its fields, their initial values, and their validation rules — is defined as data in signupForm, independent of any template. [formGroup]="signupForm" connects that object to the <form> element, and formControlName="email" connects one input to one control inside it. Because the form exists as a plain object, you can construct it, set its values, and check its validity in a unit test without rendering any HTML at all — a real advantage over template-driven forms as validation logic grows more involved.
Reading and reacting to changes
this.signupForm.get('email')?.valueChanges.subscribe((value) => {
console.log('Email changed to', value);
});
this.signupForm.patchValue({ email: 'ada@example.com' });Every FormControl (and FormGroup) exposes valueChanges as an observable — exactly the RxJS pattern from earlier in this course — letting you react to edits as they happen, useful for things like live-validating a username's availability. patchValue sets one or more fields programmatically, useful for pre-filling a form with existing data, such as when editing an existing record.
Grouping and nesting
profileForm = new FormGroup({
name: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
}),
});<div formGroupName="address">
<input formControlName="street" />
<input formControlName="city" />
</div>FormGroups nest, mirroring however the underlying data is structured — profileForm.value.address.city reads naturally because the form model matches the shape of the data it represents.
Reactive vs template-driven, in practice
Reactive forms trade a small amount of extra setup code for a form model you can test, inspect, and manipulate directly from TypeScript — which pays off as soon as a form has conditional fields, cross-field validation, or logic complex enough that you'd want a unit test for it independent of the DOM. For a short, simple form, that structure is often more ceremony than the form needs, which is exactly where template-driven forms remain a reasonable choice instead.