Template-Driven Forms
| Since: | Angular 14(2022) |
|---|
Template-Driven Forms in Angular are a way to build forms by placing directives in the HTML template. By importing FormsModule and using the ngModel directive, you can achieve two-way data binding. Since more of the code is concentrated in the template, this approach is well-suited for quickly creating relatively simple forms.
Basic Syntax
<!-- After importing FormsModule, place ngForm and ngModel in the template --> <form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)"> <!-- The combination of name attribute and [(ngModel)] is required --> <input type="text" name="username" [(ngModel)]="user.username" /> <button type="submit">Submit</button> </form>
Main Directives and Attributes
| Directive / Attribute | Description |
|---|---|
FormsModule | The module that must be imported into @NgModule or a standalone component to use template-driven forms. |
ngForm | A directive automatically applied to <form> elements that manages the validation state and values of the entire form. Can be referenced with a template reference variable (#myForm="ngForm"). |
[(ngModel)] | Two-way binds an input field to a component property. Must be used together with the name attribute. |
ngModel (one-way) | Can also be used one-way without binding, as [ngModel] (display only) or (ngModelChange) (change detection only). |
ngModelGroup | Groups multiple fields within a form, allowing validation and value retrieval to be done together. |
required / minlength, etc. | Specifying standard HTML validation attributes lets Angular automatically apply validation. |
Sample Code
An example of a simple form for entering a name and email address. Validation error messages are also displayed within the template.
<!-- contact-form.component.html -->
<!-- A contact form using template-driven forms to collect name and email address -->
<form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)">
<div>
<label for="name">Name</label>
<!-- Use the name attribute, ngModel, and required as a set -->
<input
id="name"
type="text"
name="name"
[(ngModel)]="formData.name"
required
minlength="2"
#nameField="ngModel"
/>
<!-- dirty: whether any input has occurred, invalid: whether validation failed -->
<p *ngIf="nameField.dirty && nameField.invalid">
<span *ngIf="nameField.errors?.['required']">Name is required.</span>
<span *ngIf="nameField.errors?.['minlength']">Please enter at least 2 characters.</span>
</p>
</div>
<div>
<label for="email">Email address</label>
<input
id="email"
type="email"
name="email"
[(ngModel)]="formData.email"
required
email
#emailField="ngModel"
/>
<p *ngIf="emailField.dirty && emailField.invalid">
<span *ngIf="emailField.errors?.['required']">Email address is required.</span>
<span *ngIf="emailField.errors?.['email']">Please enter a valid email address.</span>
</p>
</div>
<!-- Only allow the submit button to be pressed when the entire form is valid -->
<button type="submit" [disabled]="contactForm.invalid">Submit</button>
</form>
// contact-form.component.ts
// A contact component using template-driven forms.
// Importing FormsModule enables ngModel and ngForm.
import { Component } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { CommonModule } from '@angular/common';
interface ContactFormData {
name: string;
email: string;
}
@Component({
selector: 'app-contact-form',
templateUrl: './contact-form.component.html',
imports: [FormsModule, CommonModule],
standalone: true,
})
export class ContactFormComponent {
formData: ContactFormData = {
name: '',
email: '',
};
onSubmit(form: NgForm): void {
if (form.invalid) {
return;
}
console.log('Submitted data:', this.formData);
form.reset();
}
}
Grouping Fields with ngModelGroup
An example of managing related fields like address together with ngModelGroup.
<!-- register-form.component.html -->
<!-- Uses ngModelGroup to group address fields -->
<form #registerForm="ngForm" (ngSubmit)="onSubmit(registerForm)">
<input type="text" name="username" [(ngModel)]="user.username" required />
<!-- Fields within the address group can have their validation state checked together -->
<div ngModelGroup="address" #addressGroup="ngModelGroup">
<input type="text" name="state" [(ngModel)]="user.address.state" required />
<input type="text" name="city" [(ngModel)]="user.address.city" required />
</div>
<!-- Display an error message when the entire group is invalid -->
<p *ngIf="addressGroup.invalid && addressGroup.dirty">Please fill in all address fields.</p>
<button type="submit" [disabled]="registerForm.invalid">Register</button>
</form>
CSS Classes for Validation State
Angular automatically applies CSS classes to form elements based on validation state. You can use these to apply styles.
| CSS Class | When Applied | Description |
|---|---|---|
ng-valid | When validation succeeds | Indicates that the input value is in a valid state. |
ng-invalid | When validation fails | Indicates that the input value is in an invalid state. |
ng-pristine | When the user has not interacted at all | Indicates that the form is in its initial state. |
ng-dirty | When the user has changed a value | Indicates that input or changes have occurred at least once. |
ng-touched | When focus has been placed and then removed | Indicates that the field has been focused at least once. |
ng-untouched | When focus has not yet been placed | Indicates that the field has not yet been touched. |
<!-- Example of styling based on validation state with CSS -->
<style>
/* Show a red border for inputs that are dirty and invalid */
input.ng-dirty.ng-invalid {
border: 2px solid #e74c3c;
}
/* Show a green border for inputs that are dirty and valid */
input.ng-dirty.ng-valid {
border: 2px solid #2ecc71;
}
</style>
Important Notes
| Item | Description |
|---|---|
Required name attribute | Always specify the name attribute when using [(ngModel)]. Without it, Angular cannot register the form control and an error occurs. |
| Complex forms | For complex forms with dynamic fields or fine-grained programmatic control, reactive forms may be considered. |
| Change detection timing | ngModel relies on Zone.js change detection by default. Verify behavior when combining with the OnPush strategy. |
Forgetting FormsModule | Forgetting to import FormsModule causes ngModel to be treated as an unknown directive, resulting in a template compilation error. |
| Difficulty with unit testing | Since logic is scattered across the template, unit testing can be more difficult compared to reactive forms. |
Overview
Template-Driven Forms are a way to build forms easily in Angular. Simply import FormsModule and place ngModel to activate two-way binding and validation. Since standard HTML attributes like required and minlength can be used directly as validation rules, they can be written intuitively.
On the other hand, as forms become more complex, the template becomes larger and programmatic state control becomes more difficult. Consider choosing between this and Reactive Forms based on your form's requirements. For details on two-way binding, see ngModel.
If you find any errors or copyright issues, please contact us.