Template-Driven vs Reactive Forms — Choosing the Right Approach

📋 Table of Contents
  1. Comparison Table
  2. Common Mistakes

Angular has two form approaches: template-driven (logic in the template via directives) and reactive (logic in the component class via FormGroup/FormControl). Neither is universally superior — the right choice depends on the form’s complexity, validation requirements, and testing needs. Template-driven forms win on simplicity for straightforward use cases; reactive forms win on power and testability for complex scenarios. Knowing when to reach for each approach — and when a template-driven form has outgrown itself — is a key Angular skill.

Comparison Table

// ── Template-driven: login form ────────────────────────────────────────────
// Simple, familiar, little TypeScript
@Component({
  standalone: true,
  imports: [FormsModule],
  template: `
    <form #f='ngForm' (ngSubmit)="submit()" novalidate>
      <input name="email" [(ngModel)]="email" required email>
      <input name="password" [(ngModel)]="password" required minlength="8">
      <button [disabled]="f.invalid">Login</button>
    </form>
  `,
})
export class LoginTDComponent {
  email    = '';
  password = '';
  submit() { /* API call */ }
}

// ── Reactive: same login form ─────────────────────────────────────────────
// More code but fully typed and testable
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';

@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
      <input formControlName="email">
      <input formControlName="password">
      <button [disabled]="form.invalid">Login</button>
    </form>
  `,
})
export class LoginReactiveComponent {
  form = inject(FormBuilder).nonNullable.group({
    email:    ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(8)]],
  });
  submit() { /* use this.form.getRawValue() */ }
}

// ── When to use each ───────────────────────────────────────────────────────
// Template-driven is better when:
// ✅ Simple forms (login, newsletter signup, contact form)
// ✅ HTML-familiar developers (less TypeScript overhead)
// ✅ Quick prototyping
// ✅ Forms with few fields and simple validation

// Reactive is better when:
// ✅ Complex validation (cross-field, async uniqueness checks)
// ✅ Dynamic forms (add/remove fields programmatically)
// ✅ Unit testing (FormGroup is easily tested without DOM)
// ✅ Forms that need programmatic control (setValue, patchValue, reset with data)
// ✅ TypeScript-first teams (fully typed form values)
// ✅ Multi-step forms with shared form state

// Signs a template-driven form has outgrown itself:
// ⚠️  Adding complex cross-field validation (password === confirmPassword)
// ⚠️  Programmatically adding/removing form controls
// ⚠️  Trying to unit test form logic
// ⚠️  Complex conditional validation (field A required only if field B is set)
// → At any of these, migrate to reactive forms
Note: Both form approaches use the same underlying model: FormControl, FormGroup, and FormArray. Template-driven forms create these objects implicitly via NgModel and NgForm directives. Reactive forms create them explicitly in the component class. The validation state, CSS classes, and error objects work identically. The difference is where the form model is defined and how it is accessed — in the template (template-driven) or in the component class (reactive).
Tip: For a typical web application, use template-driven forms for authentication (login, register, password reset) and simple settings forms, and reactive forms for content creation and editing (post form, profile edit, complex settings). The BlogApp in Part 5 uses template-driven for auth and reactive for the post editor, showing the appropriate tool for each scenario. Chapter 55 covers reactive forms in depth for the more complex BlogApp forms.
Warning: Avoid mixing template-driven and reactive in the same form. You cannot use [formControl] and [(ngModel)] on the same input — they conflict. Choose one approach per form. You can have both approaches in the same application (different components use different approaches), but each form component imports either FormsModule (template-driven) or ReactiveFormsModule (reactive), not both.

Common Mistakes

Mistake 1 — Trying to unit test template-driven form logic (requires DOM manipulation)

❌ Painful — testing template-driven form state requires instantiating the full component with DOM; slow and fragile.

✅ Better — migrate to reactive forms when testability is needed; FormGroup can be tested without DOM.

Mistake 2 — Using template-driven forms for complex cross-field validation (workaround-heavy)

❌ Wrong — implementing “password must match confirm-password” in template-driven requires directive tricks.

✅ Correct — use reactive forms with a custom validator at the FormGroup level for cross-field validation.

🧠 Test Yourself

A post editor form needs: async slug uniqueness check (API call on blur), conditional required (excerpt required when category is “featured”), and a dynamic list of tags (add/remove). Which form approach is more appropriate?