Dynamic Controls and Rows Using FormArray in Angular
These two questions are closely related:
- How do you dynamically add controls?
- How do you dynamically add rows using
FormArray?
The key Angular class is FormArray.
1. How Do You Dynamically Add Controls?
A dynamic control is a form control that is added or removed at runtime, usually based on user action.
For example:
Skills
[C# ] [Remove]
[Angular ] [Remove]
[SQL ] [Remove]
[+ Add Skill]
We don't know in advance how many skills the user will enter.
Therefore, instead of creating:
skill1
skill2
skill3
we use:
FormArray
Conceptually:
FormArray
│
├── FormControl → C#
├── FormControl → Angular
├── FormControl → SQL
└── FormControl → Azure
2. Full Sample – Dynamically Add Controls
app.ts
import { Component } from '@angular/core';
import {
FormArray,
FormControl,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './app.html'
})
export class App {
employeeForm = new FormGroup({
name: new FormControl('', Validators.required),
skills: new FormArray([
new FormControl('', Validators.required)
])
});
get skills(): FormArray {
return this.employeeForm.controls.skills;
}
addSkill() {
this.skills.push(
new FormControl('', Validators.required)
);
}
removeSkill(index: number) {
this.skills.removeAt(index);
}
submitForm() {
if (this.employeeForm.valid) {
console.log(this.employeeForm.value);
} else {
this.employeeForm.markAllAsTouched();
}
}
}
app.html
<h2>Employee Form</h2>
<form
[formGroup]="employeeForm"
(ngSubmit)="submitForm()">
<div>
<label>Employee Name:</label>
<input
type="text"
formControlName="name">
</div>
<br>
<h3>Skills</h3>
<div formArrayName="skills">
@for (skill of skills.controls; track $index) {
<div>
<input
type="text"
[formControlName]="$index">
<button
type="button"
(click)="removeSkill($index)">
Remove
</button>
</div>
<br>
}
</div>
<button
type="button"
(click)="addSkill()">
Add Skill
</button>
<br><br>
<button type="submit">
Submit
</button>
</form>
3. Explanation
We create a FormArray:
skills: new FormArray([
new FormControl('', Validators.required)
])
Initially:
skills FormArray
│
└── FormControl
The form displays:
Skills
[ ]
[Add Skill]
Adding a Control
When the user clicks:
Add Skill
Angular executes:
addSkill() {
this.skills.push(
new FormControl('', Validators.required)
);
}
push() adds another control to the FormArray.
After clicking three times:
FormArray
│
├── FormControl
├── FormControl
├── FormControl
└── FormControl
The UI automatically renders those controls.
Removing a Control
We use:
removeSkill(index: number) {
this.skills.removeAt(index);
}
Suppose:
Index
0 → C#
1 → Angular
2 → SQL
Calling:
this.skills.removeAt(1);
removes:
Angular
Result:
0 → C#
1 → SQL
4. What is formArrayName?
In HTML:
<div formArrayName="skills">
This connects the HTML section to:
skills: new FormArray(...)
Then each control is connected using its index:
<input [formControlName]="$index">
Conceptually:
FormArray
skills[0] ←→ Input 1
skills[1] ←→ Input 2
skills[2] ←→ Input 3
5. Dynamically Adding Controls vs Rows
There is an important distinction.
Dynamic controls
Each item is just one FormControl:
Skills
[C#]
[Angular]
[SQL]
Structure:
FormArray
│
├── FormControl
├── FormControl
└── FormControl
Dynamic rows
Each row contains multiple fields:
Product Quantity Price
[Laptop] [2] [50000]
[Mouse ] [3] [1000]
[Keyboard] [1] [2000]
[+ Add Row]
Each row should therefore be a FormGroup.
Structure:
FormArray
│
├── FormGroup
│ ├── product
│ ├── quantity
│ └── price
│
├── FormGroup
│ ├── product
│ ├── quantity
│ └── price
│
└── FormGroup
├── product
├── quantity
└── price
This is a very common real-world use of FormArray.
6. Full Sample – Dynamically Add Rows Using FormArray
Let's create an order form.
Each row contains:
Product | Quantity | Price | Remove
app.ts
import { Component } from '@angular/core';
import {
FormArray,
FormControl,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './app.html'
})
export class App {
orderForm = new FormGroup({
customerName: new FormControl('', Validators.required),
items: new FormArray([
this.createItem()
])
});
createItem(): FormGroup {
return new FormGroup({
product: new FormControl('', Validators.required),
quantity: new FormControl(1, [
Validators.required,
Validators.min(1)
]),
price: new FormControl(0, [
Validators.required,
Validators.min(1)
])
});
}
get items(): FormArray {
return this.orderForm.controls.items;
}
addItem() {
this.items.push(
this.createItem()
);
}
removeItem(index: number) {
this.items.removeAt(index);
}
submitForm() {
if (this.orderForm.valid) {
console.log(this.orderForm.value);
} else {
this.orderForm.markAllAsTouched();
}
}
}
app.html
<h2>Order Form</h2>
<form
[formGroup]="orderForm"
(ngSubmit)="submitForm()">
<div>
<label>Customer Name:</label>
<input
type="text"
formControlName="customerName">
</div>
<br>
<h3>Order Items</h3>
<div formArrayName="items">
@for (item of items.controls; track $index) {
<div [formGroupName]="$index">
<label>Product:</label>
<input
type="text"
formControlName="product">
<label>Quantity:</label>
<input
type="number"
formControlName="quantity">
<label>Price:</label>
<input
type="number"
formControlName="price">
<button
type="button"
(click)="removeItem($index)">
Remove
</button>
</div>
<br>
}
</div>
<button
type="button"
(click)="addItem()">
Add Row
</button>
<br><br>
<button type="submit">
Submit Order
</button>
</form>
7. Understanding createItem()
The important method is:
createItem(): FormGroup {
return new FormGroup({
product: new FormControl('', Validators.required),
quantity: new FormControl(1),
price: new FormControl(0)
});
}
This method creates one row.
Think:
createItem()
↓
FormGroup
│
├── product
├── quantity
└── price
Every time we need another row, we call:
this.createItem()
8. Adding a Row
Our method:
addItem() {
this.items.push(
this.createItem()
);
}
Suppose initially:
FormArray
│
└── Row 1
User clicks Add Row:
FormArray
│
├── Row 1
└── Row 2
Clicks again:
FormArray
│
├── Row 1
├── Row 2
└── Row 3
Each row is a separate FormGroup.
9. Removing a Row
We use:
removeItem(index: number) {
this.items.removeAt(index);
}
Suppose:
0 → Laptop
1 → Mouse
2 → Keyboard
Calling:
removeItem(1);
removes the Mouse row.
Result:
0 → Laptop
1 → Keyboard
10. Understanding the HTML Structure
This part is especially important:
<div formArrayName="items">
@for (item of items.controls; track $index) {
<div [formGroupName]="$index">
<input formControlName="product">
<input formControlName="quantity">
<input formControlName="price">
</div>
}
</div>
It exactly follows the TypeScript structure.
TypeScript
orderForm
│
└── items ← FormArray
│
├── [0] ← FormGroup
│ ├── product
│ ├── quantity
│ └── price
│
└── [1] ← FormGroup
├── product
├── quantity
└── price
HTML
formGroup="orderForm"
↓
formArrayName="items"
↓
formGroupName="0"
↓
formControlName="product"
formControlName="quantity"
formControlName="price"
That relationship is the key to understanding dynamic rows.
11. Submitted Data
Suppose the user enters:
Customer: Syed
Product Quantity Price
Laptop 1 50000
Mouse 2 1000
Keyboard 1 2000
Then:
this.orderForm.value
will look approximately like:
{
customerName: 'Syed',
items: [
{
product: 'Laptop',
quantity: 1,
price: 50000
},
{
product: 'Mouse',
quantity: 2,
price: 1000
},
{
product: 'Keyboard',
quantity: 1,
price: 2000
}
]
}
This structure maps naturally to a typical ASP.NET Core request DTO:
Order
│
├── CustomerName
│
└── Items[]
├── Product
├── Quantity
└── Price
12. Important FormArray Methods
Add
this.items.push(control);
Remove
this.items.removeAt(index);
Insert at a specific position
this.items.insert(
1,
this.createItem()
);
Get a control
this.items.at(0);
Number of items
this.items.length;
Clear all items
this.items.clear();
So the commonly used methods are:
push() → Add at end
insert() → Add at position
removeAt() → Remove one
at() → Get one
clear() → Remove all
length → Number of items
13. FormControl vs FormGroup Inside FormArray
This is an important interview concept.
FormArray<FormControl>
Use when each dynamic item contains one value.
Example:
Skills
[C#]
[Angular]
[SQL]
Structure:
FormArray
│
├── FormControl
├── FormControl
└── FormControl
FormArray<FormGroup>
Use when each dynamic item contains multiple related values.
Example:
Product Qty Price
Laptop 1 50000
Mouse 2 1000
Structure:
FormArray
│
├── FormGroup
│ ├── product
│ ├── quantity
│ └── price
│
└── FormGroup
├── product
├── quantity
└── price
Easy rule
One value per row →
FormControl
Multiple values per row →FormGroup
14. Complete Flow
Page loads
↓
FormArray contains one FormGroup
↓
One row displayed
↓
User clicks "Add Row"
↓
addItem()
↓
createItem()
↓
New FormGroup created
↓
FormArray.push()
↓
FormArray contains another group
↓
@for detects it
↓
New row displayed
Removing works in reverse:
User clicks Remove
↓
removeItem(index)
↓
FormArray.removeAt(index)
↓
FormGroup removed
↓
Row disappears
15. Key Points
FormArrayis designed for dynamic collections of controls.- Use
push()to dynamically add a control or group. - Use
removeAt(index)to remove one. - Use
insert()to add at a particular index. - Use
clear()to remove all items. formArrayNameconnects the HTML to aFormArray.[formControlName]="$index"is useful when the array contains individualFormControls.[formGroupName]="$index"is used when each array element is aFormGroup.- Use
FormArray<FormControl>for simple dynamic lists such as skills. - Use
FormArray<FormGroup>for dynamic rows containing multiple fields. - A helper method such as
createItem()keeps dynamic-row creation clean and reusable.
16. Interview Questions and Answers
1. How do you dynamically add controls in Angular Reactive Forms?
Use a FormArray and add controls using push().
this.skills.push(
new FormControl('')
);
2. How do you remove a dynamic control?
Use:
this.skills.removeAt(index);
3. How do you dynamically add rows?
Create each row as a FormGroup and add that group to a FormArray.
this.items.push(
this.createItem()
);
4. Why use FormArray instead of FormGroup for dynamic rows?
A FormArray is appropriate when the number of items is dynamic and indexed.
items[0]
items[1]
items[2]
...
A FormGroup is typically used for a known set of named controls.
5. Can a FormArray contain FormGroups?
Yes. This is one of the most common patterns for dynamic rows.
FormArray
│
├── FormGroup
├── FormGroup
└── FormGroup
6. Can a FormArray contain FormControls?
Yes.
For example, a dynamic skills list:
FormArray
│
├── FormControl → C#
├── FormControl → Angular
└── FormControl → SQL
7. What is formArrayName?
formArrayName connects an HTML section to a FormArray inside the parent form.
<div formArrayName="items">
8. Why do we use [formGroupName]="$index"?
When each FormArray item is a FormGroup, its index identifies which group the HTML row should bind to.
<div [formGroupName]="$index">
9. What is the difference between push() and insert()?
this.items.push(control);
adds the control at the end.
this.items.insert(1, control);
adds it at a specific index.
10. When should you use FormArray?
Use FormArray when the number of controls is not fixed, such as:
- Skills
- Phone numbers
- Addresses
- Qualifications
- Employees
- Invoice items
- Order items
- Employment history
Interview Summary
To dynamically add controls in Angular Reactive Forms, use a
FormArrayand itspush()method. For a simple dynamic list, theFormArraycan containFormControls. For dynamic rows containing multiple fields, each row is represented by aFormGroup, and those groups are added to theFormArray. UseremoveAt()to remove controls or rows dynamically.