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-drivenReactive
Model defined inTemplate (ngModel)Component class (FormGroup/FormControl)
Data flowAsync, two-way bindingSync, explicit, immutable updates
ValidationDirectives (required, custom validator directives)Functions (Validators, custom/async validators)
TestingHarder (needs DOM)Easy (pure TS)
Dynamic formsAwkwardFormArray, add/remove controls
UseSimple formsComplex/dynamic/enterprise forms

Custom validator: (control: AbstractControl): ValidationErrors | null. Async validator returns Observable<ValidationErrors | null> (e.g. "username taken"). v21 adds experimental Signal Forms.