Reactive Forms: FormGroup and FormControl
Angular has two form systems: template-driven forms (using [(ngModel)]) and reactive forms. Template-driven forms are simple but hard to test and validate. Reactive forms define the form structure in the component class, making validation, testing, and programmatic control straightforward.
The reactive forms philosophy
Section titled “The reactive forms philosophy”In reactive forms, the form structure lives in the TypeScript class — not the template. The template binds to that structure, but the source of truth is the class:
// Class holds the form definitionfilterForm = this.fb.group({ genre: [''], year: [''], sortBy: ['popularity.desc']});
// Template binds to it// <form [formGroup]="filterForm">// <select formControlName="genre">...</select>FormBuilder
Section titled “FormBuilder”FormBuilder is a service that provides shorthand methods for creating FormGroup and FormControl instances:
import { Component, inject } from '@angular/core';import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
@Component({ standalone: true, imports: [ReactiveFormsModule], // ...})export class Browse { private fb = inject(FormBuilder);
filterForm = this.fb.group({ genre: [''], // FormControl with initial value '' year: [''], sortBy: ['popularity.desc'] });}fb.group() creates a FormGroup. Each value in the object is either:
- A single value (shorthand for a
FormControlwith that initial value):[''] - A tuple with value and validators:
['', [Validators.required]] - A nested
FormGroup:fb.group({ ... })
Binding in the template
Section titled “Binding in the template”Use [formGroup] on the form element and formControlName on inputs:
<form [formGroup]="filterForm"> <select formControlName="genre"> <option value="">All Genres</option> @for (genre of genres; track genre.id) { <option [value]="genre.id">{{ genre.name }}</option> } </select>
<select formControlName="year"> <option value="">All Years</option> @for (year of years; track year) { <option [value]="year">{{ year }}</option> } </select>
<select formControlName="sortBy"> <option value="popularity.desc">Most Popular</option> <option value="vote_average.desc">Highest Rated</option> <option value="release_date.desc">Newest</option> </select></form>ReactiveFormsModule must be in the component’s imports array.
Reading form values
Section titled “Reading form values”Access the current value of the form or a specific control:
// Get the whole form valueconst filters = this.filterForm.value;// { genre: 'action', year: '2024', sortBy: 'popularity.desc' }
// Get a specific controlconst genreControl = this.filterForm.get('genre');const currentGenre = genreControl?.value;Or access controls directly via the typed shorthand (TypeScript knows the shape):
const genre = this.filterForm.controls.genre.value;Reacting to changes
Section titled “Reacting to changes”valueChanges is an Observable that emits every time the form value changes:
ngOnInit(): void { this.filterForm.valueChanges.subscribe(values => { this.loadMovies(values); });}In the next lessons you will learn to add debounceTime and distinctUntilChanged to this stream — avoiding excessive API calls as the user changes filters.
Exercise
Section titled “Exercise”- Create a
Browsecomponent withReactiveFormsModulein imports. - Inject
FormBuilderand create asearchForm = this.fb.group({ query: [''], category: ['all'] }). - Add
<form [formGroup]="searchForm">with a text input (formControlName="query") and a select (formControlName="category") with a few options. - Add a button that calls a method to log
this.searchForm.valueto the console. - Change the input values and confirm the logged object updates.
- Reactive forms define form structure in the component class using
FormBuilder,FormGroup, andFormControl. fb.group({ controlName: [initialValue] })creates aFormGroup.- Bind the form to the template with
[formGroup]="myForm"on the form element. - Bind individual controls with
formControlName="name"on inputs. - Add
ReactiveFormsModuleto the component’simportsarray. filterForm.valueChangesis an Observable that emits whenever any control changes.