skip to content

Template-Driven Model

Template-driven forms build their controls from ngModel directives in the markup, grouped by NgForm and ngModelGroup. Interviewers test the asynchronous model creation and when this style fits.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

6

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
open as a page

In an Angular standalone component, why does [(ngModel)] fail with NG8002, and why does an ngModel inside a <form> also need a name?

level: juniorimportance: should knowfreq 62%

basics

~20 s

NgModel is declared in FormsModule, so a standalone component that does not import FormsModule has no ngModel input and the compiler reports NG8002. Inside a form, NgForm registers each ngModel under its name, so a missing name throws unless the control is marked standalone.

open as a page

In an Angular template-driven form, what do #f="ngForm" and ngModelGroup give you, and what shape does f.value take?

level: middleimportance: should knowfreq 48%

basics

~10 s

#f="ngForm" exposes the NgForm directive: its FormGroup, value, validity, submitted flag, ngSubmit event and resetForm(). ngModelGroup="address" adds a nested FormGroup, so f.value becomes an object keyed by names, with {address: {...}} for grouped fields.

open as a page

In an Angular template-driven form, why are the form's controls still empty in ngAfterViewInit, so that form.setValue() throws, although the inputs are already rendered?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Template-driven forms register each ngModel with NgForm, and apply model values, in a microtask after the current change-detection pass. During ngAfterViewInit the group is still empty, so setValue() throws; bind values through ngModel or wait for the form to settle.

open as a page

In an Angular template-driven form, how do attributes like required, minlength, pattern and email turn into validation, and why don't native browser bubbles appear?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

FormsModule ships validator directives whose selectors match attributes such as required, minlength, pattern and email on an element that also has ngModel; each adds a validator to that control. FormsModule also adds novalidate to forms, so Angular, not the browser, reports errors.

open as a page

An Angular template-driven sign-up form moves its address inputs into a child component, and they stop affecting the parent form's value and validity. Why, and how do you fix it?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

NgModel looks for its parent ControlContainer with @Host(), which stops at the child component's boundary, so the child's inputs run as standalone controls. Add viewProviders: [{provide: ControlContainer, useExisting: NgForm}] to the child, or make it a proper custom form control.

open as a page