Angular provides two form-building approaches. Template-driven forms are declarative and placed mostly in the template. Reactive forms are more programmatic, with the form model defined explicitly in the component class. Both approaches handle validation, user input, and submission, but reactive forms offer greater flexibility and testability.
Template-driven forms rely on directives in the template. You import FormsModule and use ngModel with two-way binding.
import { NgForm } from '@angular/forms';
@Component({...})
export class LoginComponent {
onSubmit(form: NgForm) {
console.log('Form submitted:', form.value);
}
}
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
<input name="email" ngModel required email placeholder="Email">
<input name="password" ngModel required minlength="6" type="password">
<button type="submit" [disabled]="loginForm.invalid">Login</button>
</form>
Use standard HTML5 validation attributes like required, minlength, maxlength, pattern, and email. Angular automatically tracks validation state and adds CSS classes (ng-valid, ng-invalid, ng-touched, ng-dirty).
<input name="phone" ngModel required pattern="[0-9]{10}" #phone="ngModel">
<div *ngIf="phone.invalid && phone.touched" class="error">
<span *ngIf="phone.errors?.['required']">Phone is required.</span>
<span *ngIf="phone.errors?.['pattern']">Enter a valid 10-digit phone.</span>
</div>
Reactive forms are built programmatically using FormGroup, FormControl, and FormArray. Import ReactiveFormsModule and define the form model in the component class.
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({...})
export class ProfileComponent {
profileForm = new FormGroup({
firstName: new FormControl('', [Validators.required, Validators.minLength(2)]),
lastName: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email]),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl('')
})
});
onSubmit() {
if (this.profileForm.valid) {
console.log('Profile saved:', this.profileForm.value);
}
}
}
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<input formControlName="firstName" placeholder="First Name">
<input formControlName="lastName" placeholder="Last Name">
<input formControlName="email" placeholder="Email">
<div formGroupName="address">
<input formControlName="street" placeholder="Street">
<input formControlName="city" placeholder="City">
</div>
<button type="submit" [disabled]="profileForm.invalid">Save</button>
</form>
FormBuilder is a service that provides syntactic sugar for creating form controls and groups:
constructor(private fb: FormBuilder) {
this.profileForm = this.fb.group({
firstName: ['', [Validators.required, Validators.minLength(2)]],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
street: [''],
city: ['']
})
});
}
Create custom validation functions for specific business rules:
export function forbiddenNameValidator(forbiddenName: RegExp): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = forbiddenName.test(control.value);
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
// Usage:
this.form = this.fb.group({
username: ['', [Validators.required, forbiddenNameValidator(/admin/i)]]
});
Use FormArray to manage a dynamic list of form controls:
this.orderForm = this.fb.group({
items: this.fb.array([this.createItem()])
});
get items() { return this.orderForm.get('items') as FormArray; }
createItem(): FormGroup {
return this.fb.group({
product: ['', Validators.required],
quantity: [1, [Validators.required, Validators.min(1)]]
});
}
addItem() { this.items.push(this.createItem()); }
removeItem(index: number) { this.items.removeAt(index); }
Display user-friendly error messages by checking the control's errors object:
<div *ngIf="firstName.invalid && (firstName.dirty || firstName.touched)">
<p *ngIf="firstName.errors?.['required']">First name is required.</p>
<p *ngIf="firstName.errors?.['minlength']">
Minimum {{ firstName.errors?.['minlength'].requiredLength }} characters.
</p>
</div>
In this chapter, you explored both template-driven and reactive forms, including validation, FormBuilder, custom validators, dynamic FormArrays, and error message display. Reactive forms give you more control and are preferred for complex form scenarios.