2026-02-25 19:28:36 +00:00
# Reactive Forms
Reactive forms provide a model-driven approach to handling form inputs. They are built around observable streams and provide synchronous access to the data model, making them more scalable and testable than template-driven forms.
## Core Classes
Reactive forms are built using these fundamental classes from `@angular/forms` :
- `FormControl` : Manages the value and validity of an individual input.
- `FormGroup` : Manages a group of controls (an object-like structure).
- `FormArray` : Manages a numerically indexed array of controls.
- `FormBuilder` : A service that provides factory methods for creating control instances.
## Setup
Import `ReactiveFormsModule` into your component.
```ts
import {Component, inject} from '@angular/core';
import {ReactiveFormsModule, FormGroup, FormControl, Validators, FormBuilder} from '@angular/forms';
@Component ({
selector: 'app-profile-editor',
imports: [ReactiveFormsModule],
templateUrl: './profile-editor.component.html',
})
2026-02-26 22:26:05 +00:00
export class ProfileEditor {
2026-02-25 19:28:36 +00:00
private fb = inject(FormBuilder);
// Using FormBuilder for concise definition
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
}),
aliases: this.fb.array([this.fb.control('')]),
});
onSubmit() {
console.warn(this.profileForm.value);
}
}
```
## Template Binding
Use directives to bind the model to the view:
- `[formGroup]` : Binds a `FormGroup` to a `<form>` or `<div>` .
- `formControlName` : Binds a named control within a group to an input.
- `formGroupName` : Binds a nested `FormGroup` .
- `formArrayName` : Binds a nested `FormArray` .
- `[formControl]` : Binds a standalone `FormControl` .
```html
< form [ formGroup ] = " profileForm " ( ngSubmit ) = " onSubmit ( ) " >
< input type = "text" formControlName = "firstName" / >
< div formGroupName = "address" >
< input type = "text" formControlName = "street" / >
< / div >
< div formArrayName = "aliases" >
@for (alias of aliases.controls; track $index) {
< input type = "text" [ formControlName ] = " $ index " / >
}
< / div >
< button type = "submit" [ disabled ] = " ! profileForm . valid " > Submit< / button >
< / form >
```
## Accessing Controls
Use getters for easy access to controls, especially for `FormArray` .
```ts
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
addAlias() {
this.aliases.push(this.fb.control(''));
}
```
## Updating Values
- `patchValue()` : Updates only the specified properties. Fails silently on structural mismatches.
- `setValue()` : Replaces the entire model. Strictly enforces the form structure.
```ts
updateProfile() {
this.profileForm.patchValue({
firstName: 'Nancy',
address: { street: '123 Drew Street' }
});
}
```
## Unified Change Events
Modern Angular (v18+) provides a single `events` observable on all controls to track value, status, pristine, touched, reset, and submit events.
```ts
import {ValueChangeEvent, StatusChangeEvent} from '@angular/forms';
this.profileForm.events.subscribe((event) => {
if (event instanceof ValueChangeEvent) {
console.log('New value:', event.value);
}
});
```
## Manual State Management
- `markAsTouched()` / `markAllAsTouched()` : Useful for showing validation errors on submit.
- `markAsDirty()` / `markAsPristine()` : Tracks if the value has been modified.
- `updateValueAndValidity()` : Manually triggers recalculation of value and status.
- Options `{ emitEvent: false }` or `{ onlySelf: true }` can be passed to most methods to control propagation.