skip to content

In Angular, how do template-driven forms differ from reactive forms, and when is a template-driven form the right choice?

level: juniorimportance: must knowfreq 75%

answer

  1. who creates the FormControl objects
  2. FormsModule versus ReactiveFormsModule
  3. directives versus validator functions
  4. asynchronous versus synchronous data flow
  5. small, static forms

basics

~20 s

Template-driven forms let ngModel and NgForm directives build the FormControl tree implicitly from the markup, bound to plain component properties; reactive forms build that tree explicitly in the class. Template-driven fits small, static forms such as a newsletter sign-up.

solid answer

~40 s

Both styles run on the same `FormControl` and `FormGroup` classes; the difference is who creates them. In a template-driven form you import `FormsModule`, bind inputs with `ngModel` and a `name`, and `NgForm` and `NgModel` create the control tree for you; the data lives in ordinary component properties and validation comes from attributes like `required` and `email`. Model updates reach the controls asynchronously, typing is minimal, and tests need to wait for the form to settle. A reactive form, from `ReactiveFormsModule`, declares the tree in the class, is synchronous and strongly typed, and suits large, dynamic or heavily validated forms. So template-driven is right for simple, fixed forms such as a newsletter sign-up, a login or a search box. Since v22 Signal Forms are a third, stable option aimed at new signal-based apps.

code

ts · 32 lines
ts
import {Component, signal} from '@angular/core';
import {FormsModule, NgForm} from '@angular/forms';

@Component({
  selector: 'app-newsletter-signup',
  imports: [FormsModule],
  template: `
    <form #f="ngForm" (ngSubmit)="subscribe(f)">
      <input name="email" type="email" [(ngModel)]="email" required email />
      <label>
        <input name="consent" type="checkbox" [(ngModel)]="consent" required />
        Send me the newsletter
      </label>
      <button type="submit" [disabled]="f.invalid">Subscribe</button>
    </form>
    @if (done()) {
      <p>Thanks for subscribing.</p>
    }
  `,
})
export class NewsletterSignup {
  email = '';
  consent = false;
  readonly done = signal(false);

  subscribe(form: NgForm): void {
    if (form.invalid) return;
    // send this.email to the subscription API here
    this.done.set(true);
    form.resetForm();
  }
}

go deeper

for a junior

Know which module each style needs, that template-driven forms bind with ngModel and a name, and a simple example of when each fits.

for a middle

Explain that both styles share FormControl and FormGroup, and what implicit creation means: asynchronous updates, attribute validation and weak typing.

for a senior

Justify the choice per form: attributes and ngModel for small static forms, an explicit tree once rules, dynamic rows or code-driven reactions appear.

for a principal

Set a team default that includes Signal Forms for new signal-based code, while keeping migration of working template-driven forms off the critical path.

## One engine, two ways to drive it Angular's forms package has a single model layer: **`FormControl`** holds one value and its validity, **`FormGroup`** aggregates named controls, and **`ControlValueAccessor`** implementations connect those controls to DOM inputs. The two classic styles differ in **who builds that model and where your data lives**. - **Template-driven forms** (from `FormsModule`): you write the form in HTML. Every element with `ngModel` and a `name` gets an `NgModel` directive that creates a `FormControl`; the `<form>` element automatically gets an **`NgForm`** directive that creates the top-level `FormGroup` and registers each control under its name. Your data stays in ordinary component properties, usually bound with `[(ngModel)]`. - **Reactive forms** (from `ReactiveFormsModule`): you build the `FormGroup` in the component class and bind it to the template with `[formGroup]` and `formControlName`. The form model itself is the source of truth. ## Side by side | Aspect | Template-driven | Reactive | |---|---|---| | Module to import | `FormsModule` | `ReactiveFormsModule` | | Form model | implicit, created by directives | explicit, created in the class | | Where the data lives | mutable component properties | the control tree's value | | Validation | attributes (`required`, `minlength`, `email`, `pattern`) and validator directives | validator functions passed to controls | | Model-to-view data flow | asynchronous (a microtask after change detection) | synchronous | | Typing | minimal; `form.value` is `any` | inferred per control | | Testing | must wait for the form to settle after change detection | set values and assert directly | ## When template-driven is the right choice Angular's own guidance describes template-driven forms as a good fit for simple forms, naming an email sign-up form as the example. Concretely: 1. **Few, fixed fields.** A newsletter sign-up with an email box and a consent checkbox, a login, a search bar. 2. **Validation that attributes can express**: required, length, pattern, email. 3. **Logic that fits in the template**, with the submit handler doing the real work. 4. **An existing template-driven codebase**, where consistency beats migration. ## When it stops fitting - **Controls added or removed at runtime**, such as repeating address rows, are awkward to manage through the template alone. - **Cross-field rules and conditional validation** need custom directives and become hard to follow. - **Reacting to value changes in code**, for example debounced lookups, is easier when you own the control objects. - **Unit testing without the DOM** is impossible, because the model only exists once the template has rendered. - **Type safety**: the value is untyped, so refactors do not catch field renames. ## A worked fit: the newsletter sign-up A newsletter box has an email field, a consent checkbox and a submit button. In template-driven style that is three attributes and one handler: - `name="email"`, `[(ngModel)]="email"`, `required` and `email` on the text input; - `name="consent"`, `[(ngModel)]="consent"` and `required` on the checkbox, which then must be ticked; - `(ngSubmit)` on the `<form>` calling a method that checks `form.invalid` and posts the email. Writing the same form reactively would add a `FormGroup` declaration, a module import and `formControlName` bindings, and would buy nothing, because no code needs to observe or reshape the controls. That asymmetry is the whole argument for the template-driven style. ## Where Signal Forms fit in Since Angular v22, **Signal Forms** (`@angular/forms/signals`) are stable: you create a form over a writable signal model with `form()` and bind fields with the `[formField]` directive. Angular's comparison guide positions them for new signal-based apps, reactive forms for complex dynamic forms, and template-driven forms for simple forms and quick prototypes. Existing template-driven and reactive code remains fully supported. ## How to answer in an interview Start with the shared engine, then name the one real difference: **implicit, directive-built model versus explicit, class-built model**. Follow with the consequences (asynchronous flow, attribute validation, weak typing, harder testing) and finish with a concrete fit: "a newsletter sign-up is template-driven; a multi-step checkout with dynamic rows is reactive." Interviewers want to hear that you choose per form, not per team fashion.

  • Do template-driven forms use FormControl at all?
    Yes. Each `NgModel` directive owns a `FormControl`, and `NgForm` owns a `FormGroup` that the controls register into by name. You can reach them through a template reference such as `#f="ngForm"` (`f.form`, `f.controls`). The difference from reactive forms is only that directives create and wire these objects instead of your class.
  • Can one component mix a template-driven form and a reactive form?
    A component can import both modules and host one form of each kind, but a single form cannot mix them: `ngModel` inside a `[formGroup]` throws an error telling you to use `formControlName` instead, unless that `ngModel` is marked standalone and stays outside the group.

saying these in an interview costs you the question

  • Template-driven forms do not use FormControl at all
  • Template-driven forms are deprecated in current Angular
  • Reactive forms need no module import in a standalone component
  • Template-driven forms update the model synchronously like reactive forms
  • Template-driven forms are the better fit for dynamic, multi-step forms