# Reactive Forms

Course: [Angular](https://www.devtutorials.ai/learn/angular) · Section: Angular Forms
Source: https://www.devtutorials.ai/learn/angular/forms/reactive-forms

> Defining forms in the component class with FormControl, FormGroup, FormBuilder and FormArray, setting values with setValue and patchValue, and nesting groups.

## What Are Reactive Forms?

A **reactive form** is a form whose structure, values and rules are defined in the component class, as objects. The template only connects its fields to those objects.

Because the form is a plain object in the class, your code can read it, change it, test it and add fields to it at any time, without touching the template.

### Real-World Example

A job application form asks for personal details, a postal address, and a list of previous employers. The user can click **Add employer** as many times as needed.

The class defines the address as a group of fields and the employers as a list that grows at run time. When the user submits, the class sends one nested object to the API.

<figure className="ngc-fig">
<svg viewBox="0 0 640 230" role="img" aria-label="A FormGroup contains a name FormControl, a nested address FormGroup with city and pin, and an employers FormArray that can grow.">
<defs><marker id="ngc-f2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" className="ngc-arrowhead" /></marker></defs><g className="ngc-node ngc-solid c-red"><rect x="230" y="10" width="180" height="46" rx="9" /><text x="320.0" y="31" textAnchor="middle" style={{fontSize:"16px"}}>FormGroup</text><text className="ngc-sub" x="320.0" y="49" textAnchor="middle">applicationForm</text></g><line className="ngc-link" x1="320" y1="58" x2="110" y2="96" /><g className="ngc-node ngc-soft c-blue"><rect x="20" y="98" width="180" height="46" rx="9" /><text x="110.0" y="118" textAnchor="middle" style={{fontSize:"15px"}}>name</text><text className="ngc-sub" x="110.0" y="136" textAnchor="middle">FormControl</text></g><line className="ngc-link" x1="320" y1="58" x2="320" y2="96" /><g className="ngc-node ngc-soft c-purple"><rect x="230" y="98" width="180" height="46" rx="9" /><text x="320.0" y="118" textAnchor="middle" style={{fontSize:"15px"}}>address</text><text className="ngc-sub" x="320.0" y="136" textAnchor="middle">FormGroup</text></g><line className="ngc-link" x1="320" y1="58" x2="530" y2="96" /><g className="ngc-node ngc-soft c-green"><rect x="440" y="98" width="180" height="46" rx="9" /><text x="530.0" y="118" textAnchor="middle" style={{fontSize:"15px"}}>employers</text><text className="ngc-sub" x="530.0" y="136" textAnchor="middle">FormArray</text></g><line className="ngc-link" x1="320" y1="146" x2="250" y2="180" /><g className="ngc-node ngc-soft c-purple ngc-pop ngc-p2"><rect x="200" y="182" width="100" height="36" rx="9" /><text x="250.0" y="205" textAnchor="middle" style={{fontSize:"13px"}}>city</text></g><line className="ngc-link" x1="320" y1="146" x2="360" y2="180" /><g className="ngc-node ngc-soft c-purple ngc-pop ngc-p2"><rect x="310" y="182" width="100" height="36" rx="9" /><text x="360.0" y="205" textAnchor="middle" style={{fontSize:"13px"}}>pin</text></g><line className="ngc-link" x1="530" y1="146" x2="452" y2="180" /><g className="ngc-node ngc-soft c-green ngc-pop ngc-p1"><rect x="420" y="182" width="64" height="36" rx="9" /><text x="452.0" y="205" textAnchor="middle" style={{fontSize:"13px"}}>[0]</text></g><line className="ngc-link" x1="530" y1="146" x2="524" y2="180" /><g className="ngc-node ngc-soft c-green ngc-pop ngc-p2"><rect x="492" y="182" width="64" height="36" rx="9" /><text x="524.0" y="205" textAnchor="middle" style={{fontSize:"13px"}}>[1]</text></g><line className="ngc-link" x1="530" y1="146" x2="596" y2="180" /><g className="ngc-node ngc-solid c-green ngc-pop ngc-p3"><rect x="564" y="182" width="64" height="36" rx="9" /><text x="596.0" y="205" textAnchor="middle" style={{fontSize:"13px"}}>+ add</text></g>
</svg>
<figcaption>Controls hold values, groups combine them, and arrays hold lists that grow at run time.</figcaption>
</figure>

## Template-Driven Forms vs Reactive Forms

Both approaches use the same browser fields and the same states, such as `dirty` and `valid`. They differ in **where the form is defined**.

| Point | Template-driven forms | Reactive forms |
|---|---|---|
| Form defined in | The template, with `ngModel` | The class, with `FormControl` and `FormGroup` |
| Module | `FormsModule` | `ReactiveFormsModule` |
| Data model | Updated by two-way binding | Read from the form: `form.value` |
| Adding fields at run time | Difficult | Easy, with `FormArray` |
| Validation rules | Attributes in the template | Functions in the class |
| Unit testing | Needs the template | Test the form object directly |
| Best for | Small, simple forms | Large, dynamic or complex forms |

Choose one approach per form. Do not mix `ngModel` and `formControlName` in the same form, because both would try to control the same field.

## `ReactiveFormsModule`

`ReactiveFormsModule` provides the directives that connect template fields to form objects in the class:

- `[formControl]` connects one field to a standalone `FormControl`.
- `[formGroup]` connects a `<form>` element to a `FormGroup`.
- `formControlName` connects a field inside that form to one control of the group.

Import it from `@angular/forms` and add it to the component's `imports` list.

```ts
import { ReactiveFormsModule } from '@angular/forms';

@Component({
  imports: [ReactiveFormsModule],
  // ...
})
```

## `FormControl`

A `FormControl` is an object that holds the value and state of **one** field. You create it in the class with an initial value, and connect it in the template with `[formControl]`.

Read the current value with `control.value`. The `valueChanges` property emits every new value, so code can react to typing.

```angular runnable title="A FormControl holds one field's value"
import { Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule],
  template: `
    <label>Delivery city: <input [formControl]="city" /></label>
    <p>Value: {{ city.value }}</p>
    <p>Changed by the user: {{ city.dirty }}</p>
    <button (click)="city.setValue('Chennai')">Set to Chennai</button>
  `,
})
export class App {
  city = new FormControl('Pune');
}
```

## `FormGroup`

A `FormGroup` combines several controls into one object. Its `value` is an object with one key for each control, and it is valid only when every control is valid.

In the template, bind the group to the `<form>` with `[formGroup]`, and each field to a control with `formControlName`.

```angular runnable title="A FormGroup combines several controls"
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule, JsonPipe],
  template: `
    <form [formGroup]="loginForm">
      <input formControlName="username" placeholder="Username" />
      <input formControlName="password" type="password" placeholder="Password" />
    </form>
    <pre>{{ loginForm.value | json }}</pre>
  `,
})
export class App {
  loginForm = new FormGroup({
    username: new FormControl('asha.k'),
    password: new FormControl(''),
  });
}
```

## Creating Reactive Forms

Creating a reactive form takes four steps:

1. Import `ReactiveFormsModule` into the component.
2. Create a `FormGroup` with one `FormControl` per field in the class.
3. Bind the `<form>` with `[formGroup]` and each field with `formControlName`.
4. Handle `(ngSubmit)` and read `form.value`.

```angular runnable title="A complete reactive form, from definition to submit"
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="ticketForm" (ngSubmit)="submit()">
      <p><input formControlName="title" placeholder="Issue title" /></p>
      <p>
        <select formControlName="priority">
          <option value="low">Low</option>
          <option value="high">High</option>
        </select>
      </p>
      <button type="submit">Create ticket</button>
    </form>
    <p>{{ result }}</p>
  `,
})
export class App {
  ticketForm = new FormGroup({
    title: new FormControl('Payment page timeout'),
    priority: new FormControl('high'),
  });
  result = '';

  submit() {
    const ticket = this.ticketForm.value;
    this.result = 'Created "' + ticket.title + '" with ' + ticket.priority + ' priority';
  }
}
```

## `FormBuilder`

`FormBuilder` is a service that creates controls, groups and arrays with less code. Inject it with `inject(FormBuilder)`.

`fb.group({ name: [''] })` creates the same `FormGroup` as `new FormGroup({ name: new FormControl('') })`. Each control is written as an array: the first item is the initial value, and the second item holds validators, covered in the next lesson.

```angular runnable title="FormBuilder creates the same form with shorter code"
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule, JsonPipe],
  template: `
    <form [formGroup]="profileForm">
      <input formControlName="firstName" />
      <input formControlName="lastName" />
      <input formControlName="phone" />
    </form>
    <pre>{{ profileForm.value | json }}</pre>
  `,
})
export class App {
  private fb = inject(FormBuilder);

  profileForm = this.fb.group({
    firstName: ['Ravi'],
    lastName: ['Shankar'],
    phone: ['98450 12345'],
  });
}
```

| Without `FormBuilder` | With `FormBuilder` |
|---|---|
| `new FormGroup({ ... })` | `fb.group({ ... })` |
| `new FormControl('Ravi')` | `['Ravi']` |
| `new FormArray([ ... ])` | `fb.array([ ... ])` |

## Setting and Updating Form Values

You often need to fill a form from code, for example after loading a saved record from an API. Reactive forms provide two methods.

### `setValue()`

`setValue()` replaces the value of the **whole** group. The object must contain **every** control of the group. If a key is missing or extra, Angular throws an error.

### `patchValue()`

`patchValue()` updates **only** the controls you include. Other controls keep their current values. Use it for partial updates.

```angular runnable title="setValue needs every field, patchValue accepts some"
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule, JsonPipe],
  template: `
    <form [formGroup]="userForm">
      <input formControlName="name" />
      <input formControlName="email" />
      <input formControlName="role" />
    </form>
    <button (click)="loadUser()">Load saved user (setValue)</button>
    <button (click)="changeRole()">Promote (patchValue)</button>
    <pre>{{ userForm.value | json }}</pre>
  `,
})
export class App {
  userForm = new FormGroup({
    name: new FormControl(''),
    email: new FormControl(''),
    role: new FormControl(''),
  });

  loadUser() {
    this.userForm.setValue({ name: 'Meena', email: 'meena@example.com', role: 'editor' });
  }

  changeRole() {
    this.userForm.patchValue({ role: 'admin' });
  }
}
```

| Point | `setValue()` | `patchValue()` |
|---|---|---|
| Updates | Every control | Only the controls you pass |
| Missing keys | Throws an error | Allowed |
| Typical use | Load a complete record | Change one or two fields |

## Nested Form Groups

A `FormGroup` can contain another `FormGroup`. Nesting keeps related fields together, such as the parts of an address, and produces a nested object in `form.value`.

In the template, wrap the nested fields in an element with `formGroupName`.

```angular runnable title="A nested group produces a nested value"
import { Component, inject } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule, JsonPipe],
  template: `
    <form [formGroup]="customerForm">
      <p><input formControlName="name" /></p>
      <fieldset formGroupName="address">
        <legend>Address</legend>
        <input formControlName="city" />
        <input formControlName="pin" />
      </fieldset>
    </form>
    <pre>{{ customerForm.value | json }}</pre>
  `,
})
export class App {
  private fb = inject(FormBuilder);

  customerForm = this.fb.group({
    name: ['Asha Kumar'],
    address: this.fb.group({
      city: ['Mumbai'],
      pin: ['400001'],
    }),
  });
}
```

## `FormArray`

A `FormArray` holds a **list** of controls or groups whose length can change at run time. Use it for repeating fields, such as phone numbers, skills or order items.

- `push(control)` adds an item.
- `removeAt(index)` removes an item.
- `controls` gives the list to loop over in the template.

In the template, wrap the list in an element with `formArrayName`, and bind each item by its index with `[formControlName]="i"`.

```angular runnable title="FormArray adds and removes fields at run time"
import { Component, inject } from '@angular/core';
import { FormArray, FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="skillsForm">
      <div formArrayName="skills">
        @for (skill of skills.controls; track skill; let i = $index) {
          <p><input [formControlName]="i" /> <button type="button" (click)="skills.removeAt(i)">Remove</button></p>
        }
      </div>
    </form>
    <button (click)="addSkill()">Add skill</button>
    <p>Skills: {{ skills.value.join(', ') }}</p>
  `,
})
export class App {
  private fb = inject(FormBuilder);

  skillsForm = this.fb.group({
    skills: this.fb.array([['Angular'], ['TypeScript']]),
  });

  get skills() {
    return this.skillsForm.get('skills') as FormArray;
  }

  addSkill() {
    this.skills.push(new FormControl('New skill'));
  }
}
```

The `skills` getter returns the array with the right type, so the template and methods can call `push`, `removeAt` and `controls`.

Use a `get` accessor for every `FormArray` and nested group you use often. It keeps the template short and avoids repeating `form.get('...')`.

Reactive form values are **typed**. `new FormControl('Pune')` has the type `FormControl<string | null>`, so TypeScript reports a mistake such as `city.value.toFixed()`. The value can be `null` because `reset()` sets it to `null`. Use `new FormControl('Pune', { nonNullable: true })` to keep it a `string`.

## Common Mistakes

<div className="ngc-mistakes">

| Mistake | What you see | Fix |
|---|---|---|
| `ReactiveFormsModule` not imported | `NG0303: Can't bind to 'formGroup'` | Add `ReactiveFormsModule` to `imports` |
| `formControlName` outside a `[formGroup]` | `formControlName must be used with a parent formGroup directive` | Put the field inside the `<form [formGroup]>` |
| Misspelled control name | `Cannot find control with name: 'emial'` | Match the key in the `FormGroup` exactly |
| `setValue()` with a missing field | `Must supply a value for form control with name: 'role'` | Pass every field, or use `patchValue()` |
| Using `[(ngModel)]` and `formControlName` together | A deprecation warning and confusing updates | Use one approach per form |
| Looping over `skillsForm.get('skills')` directly | A type error about `AbstractControl` | Add a getter that returns `FormArray` |

</div>

## Check Yourself

## Exercises

## What to Remember

- Reactive forms define the form in the class with `FormControl`, `FormGroup` and `FormArray`, and need `ReactiveFormsModule`.
- Bind with `[formGroup]`, `formControlName`, `formGroupName` and `formArrayName`. `FormBuilder` creates the same objects with less code.
- Use `setValue()` to replace every value and `patchValue()` to update some of them.

Next: **Form Validation**, how to check user input and show clear error messages.
