Roadmap › Angular (2 → 21) › Module: Routing and Forms
Lessons in this course (16)
14. Template-driven vs reactive forms
Intermediate★ Must knowLesson 14 of 16
ts
// Reactive (typed) form
private fb = inject(NonNullableFormBuilder);
form = this.fb.group({
customer: ["", [Validators.required, Validators.minLength(3)]],
email: ["", [Validators.required, Validators.email]],
lines: this.fb.array([this.newLine()]),
});
newLine() { return this.fb.group({ productId: [0, Validators.min(1)], qty: [1, [Validators.min(1), Validators.max(100)]] }); }
save() { if (this.form.invalid) { this.form.markAllAsTouched(); return; } this.api.save(this.form.getRawValue()); }html
<form [formGroup]="form" (ngSubmit)="save()">
<input formControlName="customer" />
@if (form.controls.customer.touched && form.controls.customer.hasError('required')) { <small>Required</small> }
<button [disabled]="form.invalid">Save</button>
</form>| Template-driven | Reactive | |
|---|---|---|
| Model defined in | Template (ngModel) | Component class (FormGroup/FormControl) |
| Data flow | Async, two-way binding | Sync, explicit, immutable updates |
| Validation | Directives (required, custom validator directives) | Functions (Validators, custom/async validators) |
| Testing | Harder (needs DOM) | Easy (pure TS) |
| Dynamic forms | Awkward | FormArray, add/remove controls |
| Use | Simple forms | Complex/dynamic/enterprise forms |
Custom validator: (control: AbstractControl): ValidationErrors | null. Async validator returns Observable<ValidationErrors | null> (e.g. "username taken"). v21 adds experimental Signal Forms.