Skip to main content

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 empty
  • minlength = min length
  • maxlength = max length
  • pattern = regex
  • email = 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 = unchanged
  • dirty = changed
  • touched = focus + left
  • valid/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 *ngIf on 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
  1. Missing name attribute — ngModel won't work
  2. Not importing FormsModule — ngModel not available
  3. Validating client-only — Always validate server too
  4. 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