Master template-driven forms in Angular. Learn form controls, validation, data binding, and form submission handling.
Master template-driven forms in Angular. Learn form controls, validation, data binding, and form submission handling.
To use template-driven forms, import FormsModule:
1import { Component } from '@angular/core';
2import { FormsModule } from '@angular/forms';
3
4@Component({
5 selector: 'app-contact-form',
6 standalone: true,
7 imports: [FormsModule],
8 template: \`
9 <form #contactForm="ngForm" (ngSubmit)="onSubmit(contactForm)">
10 <input type="text" name="name" [(ngModel)]="model.name" required>
11 <button type="submit">Submit</button>
12 </form>
13 \`
14})
15export class ContactFormComponent {
16 model = {
17 name: '',
18 email: '',
19 message: ''
20 };
21
22 onSubmit(form: any) {
23 if (form.valid) {
24 console.log('Form submitted:', this.model);
25 }
26 }
27}NgModel provides two-way data binding between form controls and component properties:
1<form>
2 <!-- Two-way binding with [(ngModel)] -->
3 <input
4 type="text"
5 name="username"
6 [(ngModel)]="user.username"
7 required>
8
9 <!-- One-way binding (property) -->
10 <input
11 type="text"
12 name="email"
13 [ngModel]="user.email"
14 (ngModelChange)="onEmailChange($event)">
15
16 <!-- No binding (just tracking) -->
17 <input
18 type="text"
19 name="phone"
20 ngModel>
21</form>
22
23// Component
24user = {
25 username: '',
26 email: '',
27 phone: ''
28};
29
30onEmailChange(value: string) {
31 console.log('Email changed:', value);
32}Angular provides built-in validators that can be applied as attributes:
Learn about Built-in Validators.
1<form #userForm="ngForm">
2 <!-- Required validator -->
3 <input
4 type="text"
5 name="username"
6 [(ngModel)]="user.username"
7 required
8 #username="ngModel">
9
10 <!-- Min/Max length -->
11 <input
12 type="password"
13 name="password"
14 [(ngModel)]="user.password"
15 required
16 minlength="8"
17 maxlength="20"
18 #password="ngModel">
19
20 <!-- Pattern validator -->
21 <input
22 type="email"
23 name="email"
24 [(ngModel)]="user.email"
25 required
26 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$"
27 #email="ngModel">
28
29 <!-- Min/Max value (for numbers) -->
30 <input
31 type="number"
32 name="age"
33 [(ngModel)]="user.age"
34 min="18"
35 max="100"
36 #age="ngModel">
37</form>Show validation errors conditionally based on field state:
1<form #userForm="ngForm">
2 <div class="form-group">
3 <label for="email">Email:</label>
4 <input
5 type="email"
6 id="email"
7 name="email"
8 [(ngModel)]="user.email"
9 required
10 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$"
11 #email="ngModel"
12 [class.error]="email.invalid && email.touched">
13
14 <!-- Show errors only when field is touched and invalid -->
15 @if (email.invalid && email.touched) {
16 <div class="error-message">
17 @if (email.errors?.['required']) {
18 <p>Email is required</p>
19 }
20 @if (email.errors?.['pattern']) {
21 <p>Please enter a valid email address</p>
22 }
23 </div>
24 }
25 </div>
26
27 <!-- Disable submit button if form is invalid -->
28 <button
29 type="submit"
30 [disabled]="userForm.invalid">
31 Submit
32 </button>
33</form>Angular tracks several states for forms and form controls:
valid/invalid:
Whether the control passes validation
pristine/dirty:
Whether the user has changed the value
touched/untouched:
Whether the user has focused and blurred
Async validation in progress
Learn about Using Form States.
1<form #myForm="ngForm">
2 <input name="username" [(ngModel)]="username" required #usernameField="ngModel">
3
4 <!-- Check control states -->
5 <p>Valid: {{ usernameField.valid }}</p>
6 <p>Dirty: {{ usernameField.dirty }}</p>
7 <p>Touched: {{ usernameField.touched }}</p>
8
9 <!-- Check form states -->
10 <p>Form Valid: {{ myForm.valid }}</p>
11 <p>Form Submitted: {{ myForm.submitted }}</p>
12
13 <!-- CSS classes automatically applied -->
14 <!-- .ng-valid / .ng-invalid -->
15 <!-- .ng-pristine / .ng-dirty -->
16 <!-- .ng-touched / .ng-untouched -->
17</form>Handle form submission and reset:
1import { Component } from '@angular/core';
2import { FormsModule, NgForm } from '@angular/forms';
3
4@Component({
5 selector: 'app-registration',
6 standalone: true,
7 imports: [FormsModule],
8 template: \`
9 <form #registrationForm="ngForm" (ngSubmit)="onSubmit(registrationForm)">
10 <input name="username" [(ngModel)]="user.username" required>
11 <input name="email" [(ngModel)]="user.email" required email>
12 <button type="submit" [disabled]="registrationForm.invalid">
13 Register
14 </button>
15 <button type="button" (click)="registrationForm.reset()">
16 Reset
17 </button>
18 </form>
19
20 @if (submitted) {
21 <div class="success">
22 Registration successful!
23 </div>
24 }
25 \`
26})
27export class RegistrationComponent {
28 user = {
29 username: '',
30 email: ''
31 };
32 submitted = false;
33
34 onSubmit(form: NgForm) {
35 if (form.valid) {
36 console.log('Form Data:', this.user);
37 console.log('Form Value:', form.value);
38 this.submitted = true;
39
40 // Reset form after submission
41 setTimeout(() => {
42 form.reset();
43 this.submitted = false;
44 }, 3000);
45 }
46 }
47}Working with different form control types:
1<form #surveyForm="ngForm">
2 <!-- Select dropdown -->
3 <select name="country" [(ngModel)]="survey.country" required>
4 <option value="">Choose country</option>
5 <option value="us">United States</option>
6 <option value="uk">United Kingdom</option>
7 <option value="ca">Canada</option>
8 </select>
9
10 <!-- Radio buttons -->
11 <div>
12 <label>
13 <input
14 type="radio"
15 name="gender"
16 value="male"
17 [(ngModel)]="survey.gender">
18 Male
19 </label>
20 <label>
21 <input
22 type="radio"
23 name="gender"
24 value="female"
25 [(ngModel)]="survey.gender">
26 Female
27 </label>
28 </div>
29
30 <!-- Checkboxes -->
31 <label>
32 <input
33 type="checkbox"
34 name="subscribe"
35 [(ngModel)]="survey.subscribe">
36 Subscribe to newsletter
37 </label>
38
39 <!-- Multiple checkboxes -->
40 @for (interest of interests; track interest) {
41 <label>
42 <input
43 type="checkbox"
44 [name]="interest"
45 [(ngModel)]="survey.interests[interest]">
46 {{ interest }}
47 </label>
48 }
49</form>
50
51// Component
52survey = {
53 country: '',
54 gender: '',
55 subscribe: false,
56 interests: {
57 sports: false,
58 music: false,
59 tech: false
60 }
61};
62
63interests = ['sports', 'music', 'tech'];Use template-driven forms for simple forms with basic validation
Always provide a name attribute when using ngModel
Show validation errors only after field is touched
Disable submit button when form is invalid
Provide clear, helpful error messages
Use template reference variables to access form controls
Consider reactive forms for complex forms with dynamic fields
Reset forms after successful submission
Use CSS classes (ng-valid, ng-invalid) for visual feedback
Template-driven forms use FormsModule and NgModel for two-way data binding
Forms require a name attribute on all ngModel controls for proper tracking
Built-in validators include required, minlength, maxlength, pattern, email, min, and max
Template reference variables (#myForm="ngForm") provide access to form and control states
Form controls track valid, pristine, dirty, touched, and pending states
Validation errors should be displayed only after controls are touched
NgForm automatically tracks form validity and can be used to disable submit buttons
Use (ngSubmit) to handle form submission instead of button click events
Forms can be reset with form.reset() to clear all values and states
Your progress is saved in this browser, on this device only.