On this page
Forms
Angular supports two form approaches: template-driven (simple, HTML-centric) and reactive (programmatic, scalable).
Template-Driven Forms
Import FormsModule and use ngModel:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-contact-form',
standalone: true,
imports: [FormsModule],
template: `
<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<input name="name" ngModel required #name="ngModel" placeholder="Name">
@if (name.invalid && name.touched) {
<span class="error">Name is required</span>
}
<input name="email" ngModel required email #email="ngModel" placeholder="Email">
@if (email.invalid && email.touched) {
<span class="error">Valid email required</span>
}
<button type="submit" [disabled]="form.invalid">Send</button>
</form>
`
})
export class ContactFormComponent {
onSubmit(form: { value: { name: string; email: string } }) {
console.log(form.value);
}
}
Template-driven forms are quick to prototype but harder to test and scale.
Reactive Forms
Import ReactiveFormsModule and build a form in TypeScript:
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-signup-form',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<input formControlName="username" placeholder="Username">
@if (form.get('username')?.invalid && form.get('username')?.touched) {
<span class="error">Username required (min 3 chars)</span>
}
<input formControlName="email" type="email" placeholder="Email">
<input formControlName="password" type="password" placeholder="Password">
<button type="submit" [disabled]="form.invalid">Sign Up</button>
</form>
`
})
export class SignupFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
username: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]]
});
onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}
}
Form Groups and Nested Fields
form = this.fb.group({
profile: this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required]
}),
address: this.fb.group({
city: [''],
zip: ['', Validators.pattern(/^\d{5}$/)]
})
});
Template:
<div formGroupName="profile">
<input formControlName="firstName">
<input formControlName="lastName">
</div>
FormArray — Dynamic Fields
import { FormArray } from '@angular/forms';
skills = this.fb.array([
this.fb.control('JavaScript', Validators.required)
]);
addSkill() {
this.skills.push(this.fb.control('', Validators.required));
}
get skills(): FormArray {
return this.form.get('skills') as FormArray;
}
<div formArrayName="skills">
@for (skill of skills.controls; track $index) {
<input [formControlName]="$index" placeholder="Skill">
}
<button type="button" (click)="addSkill()">Add skill</button>
</div>
When to Use Which
| Approach | Best for |
|---|---|
| Template-driven | Simple forms, quick prototypes |
| Reactive | Complex validation, dynamic fields, unit tests |
Reactive forms give you full control in TypeScript — prefer them for production applications with non-trivial validation.