Chapter 8.6โ˜• 15 min read

Template-Driven Forms

Template-driven forms rely on directives in the template. ngModel creates FormControls automatically. Less TypeScript, more HTML. Best for simple forms.

01What are Template-Driven Forms

Template-driven forms are the simpler alternative to reactive forms. Instead of creating FormControls in TypeScript, you use directives in the template that create the form model automatically.

"Template-driven = HTML pe dependent โ€” TypeScript mein kam likhna."

How it works:

  • ngModel directive on an input creates a FormControl automatically
  • name attribute is MANDATORY โ€” it becomes the control's key in the form
  • ngForm (on form element) automatically tracks the entire form state
  • You access form state via template reference variables like #myForm="ngForm"
  • Validation attributes (required, minlength) work directly in HTML

Template-driven forms are the opposite of reactive forms โ€” the template drives the logic, not the TypeScript code.

02Setup

In Angular 17+, template-driven forms use the same provider as reactive forms.

// app.config.ts โ€” both form types share this provider
import { provideForms } from '@angular/forms';

export const appConfig: ApplicationConfig = {
  providers: [
    provideForms(), // Enables BOTH reactive AND template-driven forms
  ]
};

"Same setup as reactive โ€” Angular 17+ unified both."

Old way (Angular 16 and earlier):

// Old NgModule approach
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [FormsModule], // Template-driven only
})

Now with provideForms(), you get both form types from a single provider. No need to choose between FormsModule and ReactiveFormsModule at import time.

03Creating a Template-Driven Form

Let's create a template-driven form step by step.

<!-- Component template -->
<form #biryaniForm="ngForm" (ngSubmit)="onSubmit(biryaniForm)">
  <!-- name attribute is MANDATORY for ngModel -->
  <div class="field">
    <label>Biryani Name</label>
    <input
      name="biryaniName"
      ngModel
      required
      minlength="3"
      #name="ngModel"
      placeholder="Enter biryani name" />

    @if (name.invalid && name.touched) {
      <div class="errors">
        @if (name.errors?.['required']) {
          <small class="error">Name is required</small>
        }
        @if (name.errors?.['minlength']) {
          <small class="error">Min 3 characters</small>
        }
      </div>
    }
  </div>

  <div class="field">
    <label>Category</label>
    <select name="category" ngModel required #category="ngModel">
      <option value="">Select...</option>
      <option value="chicken">Chicken</option>
      <option value="mutton">Mutton</option>
    </select>
  </div>

  <button type="submit" [disabled]="biryaniForm.invalid">
    Submit
  </button>

  <!-- Form value preview -->
  <pre>{{ biryaniForm.value | json }}</pre>
</form>

"name attribute MANDATORY โ€” Angular uses it as form control key."

#name="ngModel" creates a template reference that exposes the control's validation state. Without this, you can't access individual field validation in the template.

Component:

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-template-form',
  standalone: true,
  imports: [FormsModule, JsonPipe],
  templateUrl: './template-form.component.html',
})
export class TemplateFormComponent {
  onSubmit(form: NgForm) {
    if (form.valid) {
      console.log('Form submitted:', form.value);
      form.reset();
    }
  }
}
04Two-Way Binding with ngModel

Two-way binding with [(ngModel)] synchronizes the input value and the component property in both directions.

// Component
biryaniName = 'Chicken Dum Biryani';

// Template โ€” two-way binding
<input [(ngModel)]="biryaniName" name="biryaniName" />
<p>You typed: {{ biryaniName }}</p>

"ngModel = two-way street โ€” type karo, property update, property change karo, input update."

For FORM fields, use ngModel (without brackets) + name:

<!-- โœ… For forms โ€” registers with ngForm, validation works -->
<input name="email" ngModel required #email="ngModel" />

<!-- โŒ For forms โ€” does NOT register with ngForm -->
<input [(ngModel)]="email" name="email" />

Use [(ngModel)] only for simple non-form cases:

// Simple binding outside a form โ€” works great!
<input [(ngModel)]="searchTerm" (ngModelChange)="onSearch()" />

The key difference: ngModel (without brackets) is one-way from template to form model. [(ngModel)] is two-way between template and component property. Inside a form, prefer ngModel with name.

05When to Use Template-Driven vs Reactive

Template-driven forms are NOT always the right choice. Here's when to use each.

Use Template-Driven when:

  • Simple forms with fewer than 5 fields
  • Quick prototypes and MVPs
  • The form structure is static (no dynamic add/remove)
  • Your team prefers HTML-centric approach
  • You need a quick contact form, search box, newsletter signup

Use Reactive when:

  • Complex forms with 5+ fields
  • Dynamic forms (add/remove fields at runtime)
  • Custom validators and cross-field validation
  • Unit testing forms (reactive forms are pure TypeScript)
  • Enterprise applications (TCS, Wipro, Infosys standard)

"Simple form = template-driven, Complex form = reactive."

Rule of thumb: If a form has more than 5 fields or any dynamic behavior, use reactive forms. For simple 2-3 field forms, template-driven is faster to write.

"Hyderabad IT companies = reactive forms mostly."

Key Takeaways

  • โœ… Template-driven: ngModel creates FormControls automatically โ€” html-centric approach
  • โœ… provideForms() enables BOTH template-driven and reactive forms in Angular 17+
  • โœ… name attribute is MANDATORY โ€” ngModel won't register with ngForm without it
  • โœ… #ref="ngModel" for validation access, #form="ngForm" for form-level state
  • โœ… Template-driven for simple forms (<5 fields), Reactive for complex/dynamic forms
Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase