Template-Driven Forms
Level: Beginner
ℹ️ What You'll Learn
- Form directives (ngForm, ngModel)
- Form states (pristine, dirty, valid, invalid)
- Form validation
- Displaying errors
- Form submission
Basic Form
<form (ngSubmit)="onSubmit()" #studentForm="ngForm">
<div>
<label>Name:</label>
<input
type="text"
name="name"
[(ngModel)]="student.name"
required
#nameField="ngModel"
>
<span *ngIf="nameField.errors?.['required']">Name required</span>
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
[(ngModel)]="student.email"
required
email
#emailField="ngModel"
>
<span *ngIf="emailField.errors?.['email']">Invalid email</span>
</div>
<button type="submit" [disabled]="studentForm.invalid">
Submit
</button>
</form>
Component:
export class StudentFormComponent {
student = {
name: '',
email: ''
};
onSubmit() {
console.log('Student:', this.student);
}
}
#studentForm="ngForm" = get form reference.
[disabled]="studentForm.invalid" = disable if invalid.
Form Validation
<input
type="text"
name="rollNumber"
[(ngModel)]="student.rollNumber"
required
minlength="3"
pattern="[0-9]+"
#rollField="ngModel"
>
<div *ngIf="rollField.invalid && rollField.touched">
<span *ngIf="rollField.errors?.['required']">Roll number required</span>
<span *ngIf="rollField.errors?.['minlength']">Min 3 characters</span>
<span *ngIf="rollField.errors?.['pattern']">Only numbers</span>
</div>
Validators:
required= not emptyminlength= min lengthmaxlength= max lengthpattern= regexemail= valid email
Form States
<div [ngClass]="{'has-error': field.invalid && field.touched}">
<input #field="ngModel" [(ngModel)]="value">
</div>
<!-- Access states -->
<span *ngIf="field.pristine">Never changed</span>
<span *ngIf="field.dirty">User changed it</span>
<span *ngIf="field.touched">User focused and left</span>
<span *ngIf="field.untouched">User never focused</span>
<span *ngIf="field.valid">Valid</span>
<span *ngIf="field.invalid">Invalid</span>
States:
pristine= unchangeddirty= changedtouched= focus + leftvalid/invalid= validation
SMS Form Example
<form (ngSubmit)="registerStudent()" #form="ngForm">
<div class="form-group">
<label>Roll Number:</label>
<input
type="text"
name="rollNumber"
[(ngModel)]="student.rollNumber"
required
pattern="[0-9]+"
#rollField="ngModel"
>
<span class="error" *ngIf="rollField.invalid && rollField.touched">
Valid roll number required
</span>
</div>
<div class="form-group">
<label>Name:</label>
<input
type="text"
name="name"
[(ngModel)]="student.name"
required
minlength="3"
>
</div>
<div class="form-group">
<label>Email:</label>
<input
type="email"
name="email"
[(ngModel)]="student.email"
required
email
>
</div>
<div class="form-group">
<label>Class:</label>
<select name="className" [(ngModel)]="student.className">
<option *ngFor="let cls of classes" [value]="cls">
{{ cls }}
</option>
</select>
</div>
<button type="submit" [disabled]="form.invalid || submitting">
{{ submitting ? 'Registering...' : 'Register' }}
</button>
</form>
Component:
export class StudentFormComponent {
student = {
rollNumber: '',
name: '',
email: '',
className: '10'
};
classes = ['9', '10', '11', '12'];
submitting = false;
constructor(private studentService: StudentService) {}
registerStudent() {
this.submitting = true;
this.studentService.createStudent(this.student).subscribe({
next: () => {
alert('Student registered!');
this.submitting = false;
},
error: (err) => {
alert('Error: ' + err.message);
this.submitting = false;
}
});
}
}
Select and Checkboxes
<!-- Select -->
<select name="className" [(ngModel)]="student.className">
<option value="">Select class</option>
<option *ngFor="let cls of classes" [value]="cls">
{{ cls }}
</option>
</select>
<!-- Checkbox -->
<input
type="checkbox"
name="active"
[(ngModel)]="student.active"
>
Active
<!-- Radio -->
<label>
<input
type="radio"
name="status"
value="Active"
[(ngModel)]="student.status"
>
Active
</label>
<label>
<input
type="radio"
name="status"
value="Inactive"
[(ngModel)]="student.status"
>
Inactive
</label>
Key Takeaways
- Two-way binding with
[(ngModel)] - Form validation with HTML5 attributes
- Form states: pristine, dirty, touched, valid
- Template reference variables
#field="ngModel" (ngSubmit)= form submission- Error display with
*ngIfon errors - Form disable while submitting
💡 Backend Developer Tip
Template-driven forms = quick for simple forms. For complex forms, use Reactive Forms (next article).
⚠️ Form Issues
- Missing name attribute — ngModel won't work
- Not importing FormsModule — ngModel not available
- Validating client-only — Always validate server too
- Showing all errors — Show only touched field errors
🤖Use AI to Learn Faster
Use ChatGPT, Claude, or Copilot to go deeper on Template Forms. Try these prompts:
"What's difference between pristine and untouched?""How do you disable submit until form valid?""Why always validate server-side?""Quiz me on form validation"
💡 Tip: After reading this article, paste your own code into AI and ask "What could go wrong here and why?" — fastest way to find edge cases and deepen understanding.
nexcoding.in
Have questions on your tech stack, ongoing projects, or need one-to-one training?