Skip to content

Angular Reactive Forms

Reactive forms build the entire form structure explicitly in TypeScript, then bind a template to it. This lesson covers FormGroup, FormControl, FormArray, and FormBuilder in depth.

How Reactive Forms Work

In reactive forms, you construct a tree of FormControl and FormGroup (or FormArray) instances directly in the component class, defining values and validators explicitly, then bind the template to that structure using directives like formGroup and formControlName.

Because the form's structure and validation logic live in plain TypeScript, reactive forms are straightforward to unit test without rendering any HTML at all.

@Component({
  selector: 'app-signup-form',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <input formControlName="email" />
      <button type="submit" [disabled]="form.invalid">Sign Up</button>
    </form>
  `,
})
export class SignupFormComponent {
  private fb = inject(FormBuilder);

  form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
  });

  onSubmit() {
    console.log(this.form.value);
  }
}

FormBuilder.group() is shorthand for manually constructing a FormGroup of FormControl instances.

Building Reactive Forms

form = this.fb.group({
  name: ['', Validators.required],
  address: this.fb.group({
    city: [''],
  }),
  tags: this.fb.array([]),
});

<form [formGroup]="form">
  <input formControlName="name" />
  <div formGroupName="address">
    <input formControlName="city" />
  </div>
</form>
  • FormBuilder.group() creates a FormGroup; each key maps to a FormControl, nested FormGroup, or FormArray.
  • [formGroup] on the <form> element binds the whole group; formControlName binds an individual field.
  • formGroupName binds a nested group; formArrayName binds a nested array of controls.
  • Values can be a plain default, or a two-element array [defaultValue, validators].

Reactive Forms Cheatsheet

The classes and directives you'll use to build and bind reactive forms.

API Example Purpose
FormControl new FormControl('') A single form field
FormGroup new FormGroup({ name: ... }) A named collection of controls
FormArray new FormArray([...]) A dynamic list of controls
FormBuilder inject(FormBuilder) Shorthand for building the structures above
[formGroup] <form [formGroup]="form"> Bind the whole group to a form element
formControlName <input formControlName="email"> Bind one control by key
formGroupName <div formGroupName="address"> Bind a nested group by key
formArrayName <div formArrayName="tags"> Bind a nested array by key
form.value this.form.value Current value of the whole group
form.valueChanges this.form.valueChanges.subscribe(...) Observable stream of value changes

Dynamic Fields With FormArray

FormArray manages a dynamic, variable-length list of controls, ideal for scenarios like "add another phone number" where the number of fields isn't known in advance.

form = this.fb.group({
  phones: this.fb.array([this.fb.control('')]),
});

get phones() {
  return this.form.get('phones') as FormArray;
}

addPhone() {
  this.phones.push(this.fb.control(''));
}

<div formArrayName="phones">
  @for (phone of phones.controls; track $index) {
    <input [formControlName]="$index" />
  }
</div>
<button type="button" (click)="addPhone()">Add Phone</button>

Reacting to Value Changes

Because reactive forms are built on Observables, valueChanges and statusChanges let you react to user input reactively, useful for live previews, conditional fields, or debounced search-as-you-type inputs.

this.form.get('country')?.valueChanges.subscribe(country => {
  this.updateAvailableStates(country);
});

Setting, Patching, and Resetting Values

Reactive forms give you precise, code-first control over updating values: setValue requires every key, patchValue allows partial updates, and reset clears the form back to its initial (or a given) state.

this.form.setValue({ email: 'a@b.com' });      // must match structure exactly
this.form.patchValue({ email: 'a@b.com' });    // partial update, other fields untouched
this.form.reset();                              // clears back to initial state

Common Mistakes

  • Forgetting to import ReactiveFormsModule in a standalone component that uses formGroup/formControlName.
  • Using setValue when only updating one field, patchValue is usually what you want for partial updates.
  • Not unsubscribing from valueChanges subscriptions when a component using them is destroyed.
  • Rebuilding the entire FormGroup structure unnecessarily instead of using patchValue/reset to update existing controls.

Key Takeaways

  • Reactive forms build FormControl/FormGroup/FormArray structures explicitly in TypeScript.
  • FormBuilder is a convenient shorthand for constructing these structures.
  • formGroup, formControlName, formGroupName, and formArrayName bind the template to the form structure.
  • valueChanges and statusChanges expose the form as Observables for reactive UI updates.

Pro Tip

For forms with many fields, define a typed interface for the form's shape and use Angular's typed forms (FormGroup<MyFormShape>) so form.value and form.get() calls are fully type-checked at compile time.