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/invalidtouched/untoucheddirty/pristineenabled/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 relatedFormControls.
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
FormBuilderdoesn't replaceFormGrouporFormControl; 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
FormControlrepresents one field,FormGroupgroups named controls, andFormArraymanages a dynamic indexed collection.FormBuilderprovides a concise API for creating these reactive-form structures, whileNonNullableFormBuilderprovides similar convenience with non-nullable controls by default.