Template-driven forms are the most common way to handle user input in simple Angular applications. They are easy to write and easy to understand.
1The Power of ngModel
The ngModel directive is the star of the show. It creates a FormControl instance from a domain model and binds it to a form control element. It tracks the value, the state (touched, dirty, pristine), and the validity of the input. This automation allows you to focus on your data rather than the mechanics of DOM manipulation and event listening.
2Template References
By using the hash symbol (#), you can 'capture' the instance of the ngForm directive that Angular automatically attaches to your <form> tags. This reference gives you a powerful API in your template: you can check if the entire form is valid, access all of its values as a single JSON object, and even disable buttons based on the form's current stateβall without writing a single line of TypeScript.
3Step-by-Step Breakdown
Template-driven forms are all about simplicity. You define the form's logic directly in your HTML template.
Before you start, you MUST import the 'FormsModule' in your app.module.ts. Without it, Angular won't recognize form directives.
In the template, we use [(ngModel)] to link an input to a property in our TypeScript class. This is two-way data binding.
Checkpoint: Which directive is used for two-way data binding in Template-Driven forms?
- β[(ngModel)]
- β*ngIf
To access the state of the entire form, we can export the 'ngForm' directive to a template variable using '#'.
Now we can check if the form is valid or reset it just by using 'userForm'.
Checkpoint: If you export ngForm to a variable '#f', how would you check if the form is valid?
- βf.check()
- βf.valid
You've mastered the basics of Template-Driven forms! It's the fastest way to get data from your users.
Next, we'll see the power of Reactive forms for more complex logic.
Level Up π
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1`#templateRefs` on `ngModel` Don't Replace Real Labels
Using a template reference variable like `#emailField="ngModel"` to check validity in the template is convenient for logic, but it does nothing for accessibility β the input still needs its own explicit `<label for>` exactly as any plain HTML form would.
2Show `ngModel` Validation State as Text, Not Just a CSS Class
Angular automatically adds classes like `ng-invalid` to a control, which is easy to style visually β but a color change alone conveys nothing to a screen reader; pair it with a real error message linked via `aria-describedby`.
SEO Implications
- 1
Template-Driven Form State Lives Entirely in the Browser, Invisible to Crawlers
Like reactive forms, all `ngModel` binding and validation logic executes client-side β crawlers only ever see whatever static HTML surrounds the form, not its dynamic validation behavior.
- 2
Simpler Forms Mean Simpler, More Maintainable Surrounding Page Markup
Because template-driven forms require less component-class boilerplate, the surrounding page template often stays flatter and more directly readable β a modest indirect benefit for maintaining clean, crawlable page structure over time.
Best Practices
Always Give Each `ngModel`-Bound Input a `name` Attribute
Angular's `NgForm` tracks each control internally by its `name` attribute β a template-driven form field bound with `ngModel` but missing `name` will throw a runtime error or silently fail to register with the parent form.
Reserve Template-Driven Forms for Genuinely Simple Cases
Once a form needs dynamic fields, complex cross-field validation, or extensive unit testing, the limited component-class visibility of template-driven forms becomes a liability β that's the signal to switch to reactive forms instead.
Frequent Bugs
An `ngModel`-bound field throws an error: 'name attribute must be set'.
Every `ngModel` inside an `NgForm` (i.e., any `<form>` without `[formGroup]`) requires a `name` attribute so Angular's form directive can track it internally β add a unique `name` to the input.
A template-driven form's overall validity (`form.valid`) doesn't update even though an individual field's state clearly changed.
Check that the `<form>` element has `#form="ngForm"` correctly assigned and that every field is inside that same form element β a field rendered outside the `<form>` tag (e.g., in a modal portal) won't be tracked by that form's aggregate validity.
Real-World Examples
Simple Accessible Contact Form
A short contact form uses template-driven forms for their low boilerplate, while still properly labeling every field and surfacing validation errors as real text.
<form #contactForm="ngForm" (ngSubmit)="submit()">
<label for="email">Email</label>
<input id="email" name="email" ngModel required email #email="ngModel">
<span *ngIf="email.invalid && email.touched">Enter a valid email.</span>
</form>