Template-driven forms build most of the form's structure directly in the HTML template, using directives Angular provides through FormsModule. This lesson covers building and validating one from start to finish.
How Template-Driven Forms Work
Template-driven forms rely on the ngModel directive to bind each input to a component property, and Angular automatically builds an internal form model (an NgForm) tracking the whole form's state behind the scenes.
This approach requires very little TypeScript, most of the form's shape and validation rules are expressed directly as HTML attributes and directives.
Every ngModel-bound field needs a name attribute, Angular uses it internally to track the control.
#f="ngForm" exposes the NgForm directive instance as a template variable with .value, .valid, and .controls.
Native HTML validation attributes (required, minlength, pattern, email) double as Angular validators.
(ngSubmit) fires on submission and correctly prevents the default browser form submission.
Template-Driven Forms Cheatsheet
Directives and attributes for building forms declaratively in the template.
Directive/Attribute
Example
Purpose
ngModel
[(ngModel)]="email"
Two-way bind an input to a class property
name
name="email"
Required alongside ngModel to register the control
ngForm
#f="ngForm"
Access the whole form's state
required
required
Field must be non-empty
minlength / maxlength
minlength="3"
Length constraints
pattern
pattern="[0-9]+"
Regex validation
email
email
Validates email format
ngModelGroup
ngModelGroup="address"
Group related fields together
Reading Form and Field State
Both the whole form (via #f="ngForm") and individual fields (via a per-field template variable like #email="ngModel") expose validity and interaction state you can use to show contextual error messages.
<input name="email" [(ngModel)]="email" required email #emailField="ngModel" />
<p *ngIf="emailField.invalid && emailField.touched">
Please enter a valid email address.
</p>
Grouping Fields With ngModelGroup
Related fields (like an address's street, city, and zip) can be grouped together with ngModelGroup, producing a nested object in the overall form value instead of flat, top-level properties.
Forgetting the name attribute on an ngModel-bound input, Angular cannot register the control without it.
Showing validation errors immediately on page load instead of gating them behind .touched or .dirty.
Trying to unit test form logic that only exists inside the template, which requires rendering the component.
Using template-driven forms for a large, dynamic form with many conditional fields, which quickly becomes unwieldy.
Key Takeaways
Template-driven forms use ngModel and FormsModule to build form structure mostly in the template.
#form="ngForm" exposes the whole form's state; per-field variables expose individual control state.
Native HTML validation attributes double as Angular validators in template-driven forms.
ngModelGroup groups related fields into a nested value object.
Pro Tip
Gate validation error messages behind field.touched || field.dirty (or check submitted after form submission), showing every error the instant the page loads creates a poor, alarming first impression for users.
You now understand template-driven forms in depth. Next, learn about Reactive Forms, Angular's more scalable, code-first approach.