FormBuilder, validators, and valueChanges. All Angular tutorialsComplete guide: Angular Reactive Forms Validation — Complete Guide (2026)
GitHub · LinkedIn · About · YouTube
Last updated by Kindson Munonye — June 29, 2026
📚 Tutorial hub: Angular tutorials · CRUD series
Continue the series: Part 1: Template-driven forms · Form validation guide
Prerequisites: Complete Part 1: Template-driven forms first.
Estimated time: ~45 minutes · Last updated: June 29, 2026
📚 Browse all tutorials: Angular tutorials hub · Part 1: Template-driven forms
In this simple tutorial, we would cover Reactive Forms in Angular. This follows from Part 1. There we covered Template-Driven Forms.
According to Angular documentation, reactive forms make use of explicit and immutable approach to managing the forms state. It is also based on observable streams. Moreover, reactive forms are more predictable with synchronous access to data model.
Step 1: So you need to create a new component and setup the basic form like before. I call this component rform.
Next, add the markup [formGroup] attribute to the form. Set it’s value as well. I named it rform but you can use any other name:
[formGroup] = "rform"
Once, you do this, you have to import the Reactive form module.
Then you’ll create a field rform of type FormGroup (requires you import FormGroup as well)
Build the Form
We now need to build the form using FormBuilder. This makes it a lot easier.
First add the FormControlName attribute to all the form controls in the html. So the markup for the form controls in the HTML would be like this:
<form> <div class="form-group"> <label>id</label> <input class="form-control" formControlName="id"> </div> <div class="form-group"> <label>Name</label> <input class="form-control" formControlName="name"> </div> <div class="form-group"> <label >Department</label> <input class="form-control" formControlName="department"> </div> <select class="form-control" formControlName="country"> <option> </option> </select> <button type="submit" class="btn btn-primary">Submit</button> </form>
Next, add a FormBuilder variable as a parameter to the constructor (remember also to import FormBuilder).
Finally you build the form in the ngOnInit() method. Or just in the class. This is easy too. See the code below. So you can build any kind of form based on the html form markup.
this.rform = this.fb.group({ id: [''], name: [''], department: [''], country: [''] });
So what happens here is that the form is built and each control value is initialised as an empty string.
Form Submission
To handle form submission, we would add a submit event to the form like this:
(ngSubmit)="send()"
Then we write the event handler. We can use console.log() to check what happens when values changes.
Dropdownlist Items
We use the same method we used in Template-driven forms. Create the list of item in the ts file. Then in the html markup for the select, we have:
<select class="form-control" formControlName="country"> <option *ngFor="let country of countries" value={{country}}> {{country}} </option> </select>
Built-in Validators in Reactive Forms
Import validators from @angular/forms and pass them as the second argument in each control array:
import {{ Validators }} from '@angular/forms';
this.employeeForm = this.fb.group({{
firstname: ['', [Validators.required, Validators.minLength(2)]],
email: ['', [Validators.required, Validators.email]],
age: [null, [Validators.required, Validators.min(18), Validators.max(65)]]
}});Display errors in the template using formControlName references:
<input formControlName="email" class="form-control">
<span *ngIf="employeeForm.get('email')?.invalid && employeeForm.get('email')?.touched">
Enter a valid email
</span>Custom Validators
Create a reusable validator function. This example rejects whitespace-only names:
export function noWhitespace(control: AbstractControl): ValidationErrors | null {{
const value = (control.value || '').trim();
return value.length === 0 ? {{ whitespace: true }} : null;
}}
// Usage
firstname: ['', [Validators.required, noWhitespace]]See the full validation walkthrough in Form Validation in Angular.
Tracking Changes with valueChanges
Reactive forms expose an observable stream when any control value changes — ideal for dependent dropdowns (e.g. load states when country changes):
this.employeeForm.get('country')?.valueChanges.subscribe(country => {{
this.states = this.stateService.getStatesFor(country);
this.employeeForm.patchValue({{ state: '' }});
}});Reactive Forms in Standalone Components (Angular 19)
import {{ ReactiveFormsModule, FormBuilder }} from '@angular/forms';
@Component({{
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './employee-form.component.html'
}})
export class EmployeeFormComponent {{
constructor(private fb: FormBuilder) {{
this.employeeForm = this.fb.group({{ /* controls */ }});
}}
}}Form Submission Best Practices
- Disable the submit button when
employeeForm.invalid - Call
markAllAsTouched()on failed submit to reveal errors - Use
getRawValue()if disabled controls should be included
Next step: Form Validation in Angular — Step by Step
Working with FormArray (Dynamic Fields)
When users can add multiple phone numbers, skills, or line items, use FormArray:
import {{ FormArray, FormControl }} from '@angular/forms';
skills = this.fb.array([this.fb.control('')]);
addSkill() {{ this.skills.push(this.fb.control('')); }}
removeSkill(i: number) {{ this.skills.removeAt(i); }}
get skillControls() {{ return (this.employeeForm.get('skills') as FormArray).controls; }}<div formArrayName="skills">
<div *ngFor="let ctrl of skillControls; let i = index">
<input [formControlName]="i" class="form-control">
<button type="button" (click)="removeSkill(i)">Remove</button>
</div>
</div>
<button type="button" (click)="addSkill()">Add skill</button>Nested FormGroups
Model addresses or payment details with nested groups:
this.employeeForm = this.fb.group({{
name: ['', Validators.required],
address: this.fb.group({{
street: ['', Validators.required],
city: ['', Validators.required],
zip: ['', Validators.pattern(/^\d{{5}}$/)],
}})
}});<div formGroupName="address">
<input formControlName="street" placeholder="Street">
<input formControlName="city" placeholder="City">
</div>Async Validators (Check Username Availability)
Use async validators for server-side checks. They return an Observable:
import {{ of }} from 'rxjs';
import {{ delay, map }} from 'rxjs/operators';
function usernameTakenValidator(userService: UserService): AsyncValidatorFn {{
return (control: AbstractControl) =>
userService.isTaken(control.value).pipe(
map(taken => (taken ? {{ usernameTaken: true }} : null))
);
}}
username: ['', [Validators.required], [usernameTakenValidator(this.userService)]]Show a loading indicator while async validation runs using pending status on the control.
patchValue vs setValue
setValue()— requires every control; throws if any are missingpatchValue()— updates only provided fields; ideal for edit forms loading API datareset()— clears the form back to initial state
// Load employee for editing
this.employeeService.get(id).subscribe(emp => {{
this.employeeForm.patchValue(emp);
}});When to Choose Reactive vs Template-Driven
| Use reactive forms when | Use template-driven when |
|---|---|
| Complex validation logic | Simple login/search forms |
| Dynamic fields (FormArray) | Prototyping quickly |
| Unit testing form logic | Minimal TypeScript setup |
| Programmatic value changes | Familiar AngularJS-style binding |
Most production Angular apps prefer reactive forms for maintainability. Review Part 1 for template-driven basics, then apply validation in the validation guide.

[…] Continue the series: Part 2: Reactive Forms […]
[…] Related: Template-driven forms · Reactive forms […]
[…] is the definitive guide to validation in Angular reactive forms. It combines and extends our Reactive Forms tutorial and Form Validation guide with Angular 19 patterns, custom validators, async checks, and […]
[…] reactive forms guide for validated prompt […]