Reactive forms
In reactive forms the form's structure is described in the component class, and the template only binds the inputs to it. This way the whole form is visible in TypeScript: it is easy to check, change from code and test. This kind is usually chosen for large forms with complex rules.
Describing the form in the class
You need ReactiveFormsModule in imports and the FormGroup and FormControl classes:
// src/app/post-form/post-form.ts
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-post-form',
imports: [ReactiveFormsModule],
templateUrl: './post-form.html',
})
export class PostForm {
form = new FormGroup({
title: new FormControl('', { nonNullable: true }),
body: new FormControl('', { nonNullable: true }),
});
}FormControl is a single field with an initial value, FormGroup is a group of fields. The nonNullable: true setting means that after a reset the field returns to an empty string rather than null.
Binding in the template
<!-- src/app/post-form/post-form.html -->
<form [formGroup]="form" (ngSubmit)="save()">
<input formControlName="title" placeholder="Title" />
<textarea formControlName="body" placeholder="Text"></textarea>
<button type="submit">Publish</button>
</form>[formGroup] connects the form tag to the form object, and formControlName connects each input to a group member by name. The name attribute and ngModel are not needed here.
Submitting
// src/app/post-form/post-form.ts
private api = inject(PostApi);
save() {
const data = this.form.getRawValue();
this.api.createPost(data).subscribe(() => this.form.reset());
}getRawValue() returns a { title, body } object with strict types. After a successful request the form is cleared. Remember to import inject and PostApi.
Reading and changing from code
The form and each field have methods and properties for working from the class:
| Code | What it does |
|---|---|
form.value | Current values of all fields |
form.controls.title.value | The value of one field |
form.setValue({ title: 'A', body: 'B' }) | Set all fields at once |
form.patchValue({ title: 'A' }) | Set only some fields |
form.reset() | Restore the initial values |
form.valueChanges | An Observable that emits values on every keystroke |
For example, when editing a post, the loaded data goes into the form in one line: this.form.patchValue(post).
FormBuilder
In large forms new FormControl repeats many times. The FormBuilder service writes the same thing more briefly:
// src/app/post-form/post-form.ts
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'app-post-form',
imports: [ReactiveFormsModule],
templateUrl: './post-form.html',
})
export class PostForm {
private fb = inject(FormBuilder).nonNullable;
form = this.fb.group({
title: [''],
body: [''],
});
}The result is the same: a group of two fields that are never null. The template does not change.
Common mistake
Importing FormsModule instead of ReactiveFormsModule. The build then reports that it cannot bind to formGroup since it is not a known property of form. Reactive forms need exactly ReactiveFormsModule.