Forms

Chapter 9
Reactive Forms
The same forms, defined explicitly in the component class — more code, far more control

Template-driven forms (Chapter 8) put the form's structure in the HTML, with Angular inferring the model behind the scenes. Reactive forms invert that: the form is defined explicitly in the component class as an object you build and control directly, with the template just connecting to it. More setup, but the form becomes a real, inspectable, programmatically-controllable object — the better fit for complex forms, dynamic fields, and custom validation.

Building a FormGroup

// login.component.ts import { Component } from '@angular/core'; import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; @Component({ selector: 'app-login', standalone: true, imports: [ReactiveFormsModule], templateUrl: './login.component.html', }) export class LoginComponent { loginForm = new FormGroup({ email: new FormControl('', [Validators.required, Validators.email]), password: new FormControl('', [Validators.required, Validators.minLength(8)]), }); onSubmit() { console.log(this.loginForm.value); } }

A FormGroup is the whole form; each field is a FormControl with its initial value and an array of validators. Crucially, this entire structure is a plain object in the class — readable and writable from code. The validators come from Validators (required, email, minLength(8), pattern(...)) rather than being template attributes, and uses ReactiveFormsModule instead of FormsModule.

Connecting the Template

<!-- login.component.html --> <form [formGroup]="loginForm" (ngSubmit)="onSubmit()"> <input formControlName="email" /> <input formControlName="password" type="password" /> <button type="submit" [disabled]="loginForm.invalid">Log in</button> </form>

[formGroup]="loginForm" binds the template's form to the object built in the class, and each input declares which control it represents with formControlName="email". Notice there's no [(ngModel)] anywhere — the form object is the single source of truth, and the inputs simply attach to its existing controls by name. loginForm.invalid works exactly as before for the submit button.

FormBuilder — Less Boilerplate

import { FormBuilder, Validators } from '@angular/forms'; import { inject } from '@angular/core'; export class LoginComponent { private fb = inject(FormBuilder); loginForm = this.fb.group({ email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(8)]], }); }

FormBuilder — injected as a service (Chapter 6) — is a shorthand for building the same structure with less new FormGroup/new FormControl repetition: each field becomes a terse [initialValue, validators] array. It produces an identical FormGroup; it's purely the more concise and conventional way to write one, and what most real reactive-forms code uses.

Reacting to Value Changes

this.loginForm.controls.email.valueChanges.subscribe((value) => { console.log('email changed to:', value); });

Every control (and the form itself) exposes a valueChanges Observable that emits each time its value changes — the core reason these are called reactive forms. This is what makes things like live-filtering, dependent fields, or debounced validation natural, in a way template-driven forms can't match cleanly. Observables are covered properly in Chapter 11; this is a first glimpse of why reactive forms unlock so much.

Programmatic Control

this.loginForm.setValue({ email: 'a@b.com', password: 'secret123' }); // set all fields this.loginForm.patchValue({ email: 'new@b.com' }); // set just some this.loginForm.reset(); // clear everything back to initial

Because the form is a real object, it can be manipulated from code — setValue/patchValue to fill it (e.g. loading an existing record into an edit form), reset() to clear it. This programmatic control is the practical payoff of reactive forms, and the main reason they're preferred for anything beyond a simple contact form.

Don't mix the two form approaches on the same form
A single form should be entirely template-driven ([(ngModel)] + FormsModule) or entirely reactive (formControlName + ReactiveFormsModule) — mixing [(ngModel)] and formControlName on controls within the same form causes confusing conflicts. Pick one approach per form: template-driven for simple cases, reactive when the extra control is genuinely needed.
Template-driven (Ch 8)Reactive (Ch 9)
Form defined inThe template (HTML)The component class
ModuleFormsModuleReactiveFormsModule
Binding[(ngModel)]formControlName
ValidatorsTemplate attributesValidators in the class
Best forSimple formsComplex/dynamic forms, programmatic control

Coding Challenges

Challenge 1

Rebuild Chapter 8's signup form (name + email, both required, email validated) as a reactive form using FormGroup/FormControl, logging the form's value on submit and disabling the button while invalid.

📄 View solution
Challenge 2

Rewrite the same form using FormBuilder (fb.group) instead of new FormGroup/new FormControl, and add per-field validation messages reading each control's errors and touched state.

📄 View solution
Challenge 3

Build a reactive form with a "Fill demo data" button (using patchValue) and a "Reset" button (using reset()), plus subscribe to one field's valueChanges to log its value live as the user types.

📄 View solution

Chapter 9 Quick Reference

  • Reactive forms define the form in the class; require ReactiveFormsModule
  • FormGroup = the whole form; FormControl = one field (initial value + validators)
  • Template connects with [formGroup] and formControlName — no ngModel
  • Validators (required/email/minLength/pattern) come from the class, not template attributes
  • FormBuilder (fb.group({...})) — the concise, conventional way to build the same structure
  • valueChanges Observable, plus setValue/patchValue/reset — programmatic power over the form
  • Never mix ngModel and formControlName on the same form
  • Next chapter: routing — RouterModule, route params, and guards