Angular Reactive Forms for Beginners

Angular Tutorial: Beginner to Advanced · 8. Forms and validation · 3 min read · Updated

Forms are where users give your app information: signing up, searching, paying. Angular has two form systems. This lesson starts with reactive forms, which define the form in your class so that it is easy to test and validate.

What you will learn

  • How to define a form with FormGroup and FormControl
  • How to connect the form to a template
  • How to submit, read values and reset the form

Set up

Reactive forms live in @angular/forms, which is already installed in a new project. You import ReactiveFormsModule in the components that use it.

Define the form

A form is a group of controls. Each control holds one value. The FormBuilder makes creating them short. Its nonNullable helper means a control resets to its starting value instead of to null, and it gives you simpler types:

// src/app/contact-form/contact-form.ts
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-contact-form',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()">
      <p>
        <label for="name">Name</label><br />
        <input id="name" formControlName="name" />
      </p>
      <p>
        <label for="email">Email</label><br />
        <input id="email" type="email" formControlName="email" />
      </p>
      <p>
        <label for="message">Message</label><br />
        <textarea id="message" formControlName="message" rows="3"></textarea>
      </p>
      <button type="submit">Send</button>
      <button type="button" (click)="form.reset()">Clear</button>
    </form>

    @if (sent()) {
      <p role="status">Thanks, {{ sent()?.name }}. We will reply to {{ sent()?.email }}.</p>
    }
  `,
})
export class ContactForm {
  private readonly fb = inject(FormBuilder);

  protected readonly form = this.fb.nonNullable.group({
    name: '',
    email: '',
    message: '',
  });

  protected readonly sent = signal<{ name: string; email: string } | null>(null);

  protected submit(): void {
    const value = this.form.getRawValue();
    this.sent.set({ name: value.name, email: value.email });
    this.form.reset();
  }
}
// src/app/app.ts
import { Component } from '@angular/core';
import { ContactForm } from './contact-form/contact-form';

@Component({
  imports: [ContactForm],
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
  <h1>Contact us</h1>
  <app-contact-form />
</main>

How the pieces connect

  • [formGroup]="form" links the <form> element to the group in your class.
  • formControlName="name" links an input to the control with that name. The name is plain text, so there are no brackets.
  • (ngSubmit)="submit()" runs when the form is submitted, whether by the button or the Enter key.
  • form.reset() puts every control back to its starting value.

Reading values

Inside the class, form.value and form.getRawValue() give you an object with the current values. Because the form was built with nonNullable, the values are typed as strings. Use getRawValue() when you want every control, including disabled ones.

You can also read and change a single control:

// read a control's value
const email = this.form.controls.email.value;

// change values from code
this.form.patchValue({ name: 'Alex' });

patchValue updates some controls, and setValue requires all of them.

Why use a FormGroup at all?

The group tracks the state of the whole form for you: whether it is valid, whether it has been touched or changed, and what its values are. In the next lesson you will use that state to show helpful validation messages.

Common mistakes

  • Forgetting `ReactiveFormsModule`: you will get an error that formGroup is not a known property.
  • Mismatched control names: formControlName="mail" when the control is email fails at runtime. Check spelling.
  • Writing `[formControlName]`: the name is a string, so use it without brackets.

Try it yourself

  1. Add a subject control and field.
  2. Add a button that fills the form with sample data using patchValue.
  3. Show the current form value on the page with {{ form.value | json }} (remember to import JsonPipe).

Quick check

  1. What does formControlName do?
  2. Which method puts every control back to its starting value?
  3. What event runs when the form is submitted?

Next, make sure users enter sensible data with form validation. Reference: the reactive forms guide.

Frequently asked questions

Reactive or template-driven forms: which should I learn first?

Reactive forms. They scale better, are easier to test and are what most teams use. Template-driven forms are covered in a later lesson, because you will meet them too.

What are signal forms?

A newer forms API built on signals, which is evolving. Reactive forms are stable, widely used and the right starting point, and the concepts carry over.