← Back to Articles         
FormControl, FormGroup, FormArray, FormBuilder

FormControl, FormGroup, FormArray, FormBuilder

Published on 01 Oct 2026     9 min read Angular
Angular forms

Angular Reactive Forms: FormControl, FormGroup, FormArray, FormBuilder & NonNullableFormBuilder

These are core APIs used to build Reactive Forms.

The easiest way to understand them is:

FormControl
    ↓
One form field

FormGroup
    ↓
Group of form fields

FormArray
    ↓
Dynamic list of form fields/groups

FormBuilder
    ↓
Convenient way to create the above

NonNullableFormBuilder
    ↓
FormBuilder whose controls don't use null values

1. What is FormControl?

What is it?

FormControl represents one individual form field.

For example:

Name:  [ Syed             ]
        ↑
    FormControl

A FormControl manages:

  • Current value
  • Validation
  • Validation errors
  • valid / invalid
  • touched / untouched
  • dirty / pristine
  • enabled / disabled

Full Simple Program

app.ts

import { Component } from '@angular/core';
import {
  FormControl,
  ReactiveFormsModule,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './app.html'
})
export class App {

  name = new FormControl('', [
    Validators.required,
    Validators.minLength(3)
  ]);

  showValue() {
    console.log(this.name.value);
  }
}

app.html

<h2>FormControl Example</h2>

<label>Name:</label>

<input
  type="text"
  [formControl]="name">

@if (name.invalid && name.touched) {
  <p>Please enter at least 3 characters.</p>
}

<br><br>

<button (click)="showValue()">
  Show Value
</button>

<p>Value: {{ name.value }}</p>
<p>Valid: {{ name.valid }}</p>
<p>Touched: {{ name.touched }}</p>
<p>Dirty: {{ name.dirty }}</p>

Explanation

We create one control:

name = new FormControl('');

and connect it to the input:

<input [formControl]="name">

Now:

HTML textbox
     ↕
FormControl
     ↓
Value
Validation
State
Errors

If the user types Syed:

this.name.value

returns:

Syed

Key Point

FormControl = one form field.


2. What is FormGroup?

What is it?

FormGroup is used to group multiple form controls together.

For example:

Registration Form
       ↓
    FormGroup
       │
       ├── Name  → FormControl
       ├── Email → FormControl
       └── Age   → FormControl

Full Simple Program

app.ts

import { Component } from '@angular/core';
import {
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './app.html'
})
export class App {

  userForm = new FormGroup({

    name: new FormControl('', Validators.required),

    email: new FormControl('', [
      Validators.required,
      Validators.email
    ]),

    age: new FormControl<number | null>(null)

  });

  submitForm() {
    console.log(this.userForm.value);
  }
}

app.html

<h2>FormGroup Example</h2>

<form
  [formGroup]="userForm"
  (ngSubmit)="submitForm()">

  <label>Name:</label>

  <input
    type="text"
    formControlName="name">

  <br><br>

  <label>Email:</label>

  <input
    type="email"
    formControlName="email">

  <br><br>

  <label>Age:</label>

  <input
    type="number"
    formControlName="age">

  <br><br>

  <button
    type="submit"
    [disabled]="userForm.invalid">

    Submit

  </button>

</form>

Explanation

We created:

userForm = new FormGroup({

This represents the complete form.

Inside it:

name: new FormControl('')
email: new FormControl('')
age: new FormControl(...)

represent the individual fields.

So:

FormGroup
│
├── FormControl
├── FormControl
└── FormControl

Key Point

FormGroup = group of related FormControls.


3. What is FormArray?

What is it?

FormArray is used when you need a dynamic collection of form controls or form groups.

For example, suppose a user can enter multiple skills:

Skills

[C#        ]
[Angular   ]
[SQL       ]

[Add Skill]

The number of skills isn't fixed.

This is a good use case for:

FormArray

Full Simple Program

app.ts

import { Component } from '@angular/core';
import {
  FormArray,
  FormControl,
  ReactiveFormsModule
} from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './app.html'
})
export class App {

  skills = new FormArray([
    new FormControl('')
  ]);

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

  removeSkill(index: number) {
    this.skills.removeAt(index);
  }

  showSkills() {
    console.log(this.skills.value);
  }
}

app.html

<h2>Skills</h2>

<div>

  @for (skill of skills.controls; track $index) {

    <div>

      <input
        type="text"
        [formControl]="skill">

      <button
        type="button"
        (click)="removeSkill($index)">

        Remove

      </button>

    </div>

    <br>
  }

</div>

<button
  type="button"
  (click)="addSkill()">

  Add Skill

</button>

<br><br>

<button
  type="button"
  (click)="showSkills()">

  Show Skills

</button>

Explanation

Initially:

skills = new FormArray([
  new FormControl('')
]);

Conceptually:

FormArray
   │
   └── FormControl

When the user clicks Add Skill:

this.skills.push(
  new FormControl('')
);

it becomes:

FormArray
   │
   ├── FormControl → C#
   ├── FormControl → Angular
   └── FormControl → SQL

To remove one:

this.skills.removeAt(index);

Key Point

FormArray = dynamic collection of controls/groups.


4. What is FormBuilder?

What is it?

FormBuilder is an Angular service that provides a shorter and cleaner syntax for creating Reactive Forms.

Without FormBuilder:

userForm = new FormGroup({

  name: new FormControl(''),

  email: new FormControl(''),

  age: new FormControl<number | null>(null)

});

Using FormBuilder:

userForm = this.fb.group({

  name: [''],

  email: [''],

  age: [null]

});

Both create essentially the same form structure.


Full Simple Program

app.ts

import { Component, inject } from '@angular/core';
import {
  FormBuilder,
  ReactiveFormsModule,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './app.html'
})
export class App {

  private fb = inject(FormBuilder);

  userForm = this.fb.group({

    name: ['', [
      Validators.required,
      Validators.minLength(3)
    ]],

    email: ['', [
      Validators.required,
      Validators.email
    ]],

    age: [0]

  });

  submitForm() {

    if (this.userForm.valid) {
      console.log(this.userForm.value);
    }

  }
}

app.html

<h2>FormBuilder Example</h2>

<form
  [formGroup]="userForm"
  (ngSubmit)="submitForm()">

  <label>Name:</label>

  <input
    type="text"
    formControlName="name">

  <br><br>

  <label>Email:</label>

  <input
    type="email"
    formControlName="email">

  <br><br>

  <label>Age:</label>

  <input
    type="number"
    formControlName="age">

  <br><br>

  <button
    type="submit"
    [disabled]="userForm.invalid">

    Submit

  </button>

</form>

Explanation

First we get FormBuilder through DI:

private fb = inject(FormBuilder);

Then:

this.fb.group(...)

creates a FormGroup.

Instead of:

name: new FormControl('')

we can write:

name: ['']

Validators

We can also write:

name: ['', [
  Validators.required,
  Validators.minLength(3)
]]

The first item is the initial value:

''

The second is the validators:

[
  Validators.required,
  Validators.minLength(3)
]

Key Point

FormBuilder doesn't replace FormGroup or FormControl; it provides a convenient API for creating them.


5. What is NonNullableFormBuilder?

What is it?

NonNullableFormBuilder is a FormBuilder variant that creates controls whose values are non-nullable by default.

This is particularly useful with TypeScript's strict null checking.

Consider normal FormBuilder:

private fb = inject(FormBuilder);

form = this.fb.group({
  name: ['']
});

The control's value can generally be inferred as:

string | null

Why null?

Because Angular controls can normally be reset to:

null

Problem

Suppose you want:

name: string

instead of:

name: string | null

That's where NonNullableFormBuilder is useful.


Full Simple Program

app.ts

import { Component, inject } from '@angular/core';
import {
  NonNullableFormBuilder,
  ReactiveFormsModule,
  Validators
} from '@angular/forms';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './app.html'
})
export class App {

  private fb = inject(NonNullableFormBuilder);

  userForm = this.fb.group({

    name: ['', [
      Validators.required
    ]],

    email: ['', [
      Validators.required,
      Validators.email
    ]],

    age: [0]

  });

  submitForm() {

    if (this.userForm.valid) {

      const user = this.userForm.getRawValue();

      console.log(user);

    }

  }

  resetForm() {
    this.userForm.reset();
  }
}

app.html

<h2>NonNullableFormBuilder Example</h2>

<form
  [formGroup]="userForm"
  (ngSubmit)="submitForm()">

  <label>Name:</label>

  <input
    type="text"
    formControlName="name">

  <br><br>

  <label>Email:</label>

  <input
    type="email"
    formControlName="email">

  <br><br>

  <label>Age:</label>

  <input
    type="number"
    formControlName="age">

  <br><br>

  <button type="submit">
    Submit
  </button>

  <button
    type="button"
    (click)="resetForm()">

    Reset

  </button>

</form>

6. Why is NonNullableFormBuilder Useful?

Consider:

private fb = inject(NonNullableFormBuilder);

and:

name: ['']

Angular knows the control should contain a:

string

rather than:

string | null

Similarly:

age: [0]

is:

number

instead of:

number | null

So your code becomes easier to work with:

const user = this.userForm.getRawValue();

user.name.toUpperCase();

You don't have to repeatedly handle a possible null just because of the form control's default nullability.


7. Important Difference When Resetting

This is an important interview point.

Suppose we have normal nullable behavior:

new FormControl('Syed')

A reset can produce:

control.reset();

control.value
// null

With a non-nullable control:

new FormControl('Syed', {
  nonNullable: true
})

after:

control.reset();

the control resets to its initial value:

Syed

rather than null.

NonNullableFormBuilder conveniently applies this non-nullable behavior to the controls it creates.


8. FormBuilder vs NonNullableFormBuilder

FormBuilder NonNullableFormBuilder
Standard builder Non-nullable builder
Controls may include null Automatically created controls are non-nullable
string | null is common string
number | null is common number
Reset may reset nullable controls to null Reset returns controls to initial values
Useful generally Very useful with strict TypeScript

Standard

private fb = inject(FormBuilder);

form = this.fb.group({
  name: ['']
});

Think:

name: string | null

Non-nullable

private fb = inject(NonNullableFormBuilder);

form = this.fb.group({
  name: ['']
});

Think:

name: string

9. Relationship Between All Five

This is the important part to understand:

                    Reactive Forms
                          │
          ┌───────────────┼───────────────┐
          │               │               │
     FormControl      FormGroup       FormArray
          │               │               │
       One field      Group of         Dynamic
                       fields            list
                          ↑
                          │
                     FormBuilder
                          │
                   Convenient API
                          │
                          ↓
              NonNullableFormBuilder
                          │
                   Non-nullable
                   controls by default

10. Real-World Example

Imagine an Employee form:

Employee Form
│
├── Name
├── Email
├── Department
│
└── Skills
     ├── C#
     ├── Angular
     └── SQL

You might model it as:

Employee Form
      ↓
FormGroup
│
├── name
│    └── FormControl
│
├── email
│    └── FormControl
│
├── department
│    └── FormControl
│
└── skills
     └── FormArray
          ├── FormControl
          ├── FormControl
          └── FormControl

And instead of manually writing:

new FormGroup(...)
new FormControl(...)
new FormArray(...)

you can use:

FormBuilder

or:

NonNullableFormBuilder

to create the structure more concisely.


11. Key Points

API Purpose
FormControl Represents one form field
FormGroup Groups related controls
FormArray Manages a dynamic collection
FormBuilder Convenient API for building reactive forms
NonNullableFormBuilder Builds non-nullable controls by default

Remember

FormControl
    =
ONE FIELD


FormGroup
    =
GROUP OF FIELDS


FormArray
    =
DYNAMIC LIST


FormBuilder
    =
SHORTER FORM CREATION


NonNullableFormBuilder
    =
SHORTER FORM CREATION
+
NON-NULLABLE CONTROLS

12. Interview Questions and Answers

1. What is FormControl?

FormControl represents an individual form field and manages its value, validation, errors, and state.

name = new FormControl('');

2. What is FormGroup?

FormGroup groups multiple controls together and tracks the state and validity of the group.

form = new FormGroup({
  name: new FormControl(''),
  email: new FormControl('')
});

3. What is FormArray?

FormArray represents a dynamic collection of controls where controls can be added or removed at runtime.

skills = new FormArray([
  new FormControl('')
]);

4. What is FormBuilder?

FormBuilder is an injectable Angular service that provides a convenient API for creating FormGroup, FormControl, and FormArray instances.

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

5. Does FormBuilder replace FormGroup?

No.

FormBuilder is simply a convenient way to create Angular form controls and containers.

FormBuilder
     ↓
creates
     ↓
FormGroup / FormControl / FormArray

6. What is NonNullableFormBuilder?

NonNullableFormBuilder is a FormBuilder variant that creates non-nullable controls by default.

For example:

name: string

instead of commonly dealing with:

name: string | null

7. What is the difference between FormGroup and FormArray?

FormGroup usually represents a set of named controls:

name
email
age

FormArray represents an indexed collection:

skills[0]
skills[1]
skills[2]

Use FormArray when the number of controls can change dynamically.


8. When should I use FormArray?

Use it when users can dynamically add/remove repeated items such as:

  • Skills
  • Phone numbers
  • Addresses
  • Order items
  • Qualifications
  • Employment history

9. FormBuilder vs NonNullableFormBuilder?

The major difference is nullability.

FormBuilder
    ↓
Controls can commonly be nullable


NonNullableFormBuilder
    ↓
Created controls are non-nullable by default

Interview Summary

FormControl represents one field, FormGroup groups named controls, and FormArray manages a dynamic indexed collection. FormBuilder provides a concise API for creating these reactive-form structures, while NonNullableFormBuilder provides similar convenience with non-nullable controls by default.

← Back to Articles