Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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:

CodeWhat it does
form.valueCurrent values of all fields
form.controls.title.valueThe 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.valueChangesAn 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.

Thanks for reading!

Donate to the site — we work hard for you.