Creating and Using FormArray
FormArray holds a dynamic list of form controls. It is ideal for repeating rows like cart items, phone numbers, or addresses.
Creating a FormArray
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-order-form',
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="orderForm" (ngSubmit)="onSubmit()">
<div formArrayName="items">
@for (item of items.controls; track item; let i = $index) {
<div [formGroupName]="i">
<input formControlName="product" placeholder="Product">
<input formControlName="quantity" type="number" placeholder="Qty">
<button type="button" (click)="removeItem(i)">Remove</button>
</div>
}
</div>
<button type="button" (click)="addItem()">Add Item</button>
<button type="submit">Submit Order</button>
</form>
`,
})
export class OrderFormComponent {
private fb = inject(FormBuilder);
orderForm = this.fb.group({
items: this.fb.array([this.createItem()]),
});
get items(): FormArray {
return this.orderForm.get('items') as FormArray;
}
createItem(): FormGroup {
return this.fb.group({
product: ['', Validators.required],
quantity: [1, [Validators.required, Validators.min(1)]],
});
}
addItem(): void {
this.items.push(this.createItem());
}
removeItem(index: number): void {
this.items.removeAt(index);
}
onSubmit(): void {
if (this.orderForm.valid) {
console.log('Order:', this.orderForm.value);
}
}
}
FormArray Methods
| Method | Purpose |
|---|---|
push(control) |
Add a control to the end |
removeAt(index) |
Remove control at index |
insert(index, control) |
Insert control at index |
at(index) |
Get control at index |
controls |
Get all controls array |
length |
Get number of controls |
reset() |
Reset all controls |
clear() |
Remove all controls |
Validating FormArray Contents
FormArray supports both per-item validation and array-level validation.
Per-Item Validation
createItem(): FormGroup {
return this.fb.group({
product: ['', [Validators.required]],
quantity: [1, [Validators.required, Validators.min(1), Validators.max(99)]],
});
}
Array-Level Validation
export function minItemsValidator(min: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const array = control as FormArray;
return array.length >= min ? null : { minItems: { required: min, actual: array.length } };
};
}
this.orderForm = this.fb.group({
items: this.fb.array([this.createItem()], minItemsValidator(1)),
});
Displaying Validation Errors for Array Items
<div formArrayName="items">
@for (item of items.controls; track item; let i = $index) {
<div [formGroupName]="i">
<input formControlName="product" placeholder="Product">
@if (item.get('product')?.invalid && item.get('product')?.touched) {
<p class="error">Product is required</p>
}
<input formControlName="quantity" type="number">
@if (item.get('quantity')?.invalid && item.get('quantity')?.touched) {
<p class="error">Quantity must be between 1 and 99</p>
}
</div>
}
</div>
Complete Example: Dynamic Phone Numbers
@Component({
selector: 'app-phone-form',
template: `
<form [formGroup]="form">
<div formArrayName="phones">
@for (phone of phones.controls; track phone; let i = $index) {
<div [formGroupName]="i">
<input formControlName="number" placeholder="Phone number">
<select formControlName="type">
<option value="mobile">Mobile</option>
<option value="home">Home</option>
<option value="work">Work</option>
</select>
<button type="button" (click)="removePhone(i)">Remove</button>
</div>
}
</div>
<button type="button" (click)="addPhone()">Add Phone</button>
</form>
`,
})
export class PhoneFormComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
phones: this.fb.array([this.createPhone()]),
});
get phones(): FormArray {
return this.form.get('phones') as FormArray;
}
createPhone(): FormGroup {
return this.fb.group({
number: ['', [Validators.required, Validators.pattern(/^\d{10}$/)]],
type: ['mobile'],
});
}
addPhone(): void {
this.phones.push(this.createPhone());
}
removePhone(index: number): void {
this.phones.removeAt(index);
}
}
Quiz
1. What is FormArray used for?
2. How do you add a new control to a FormArray?
3. How do you render FormArray controls in a template?
Flashcards
Question
What is FormArray for?
Click to reveal answer
Answer
Dynamic lists of form controls. Add with push(), remove with removeAt().
Question
How do you render FormArray in a template?
Click to reveal answer
Answer
formArrayName on container, [formGroupName]="i" on each item.
Question
How do you validate FormArray contents?
Click to reveal answer
Answer
Per-item validators on each FormGroup, or array-level validators on the FormArray itself.
Revision Notes
Key Takeaways
- 1. FormArray holds dynamic lists of form controls
- 2. push() adds controls, removeAt() removes them
- 3. formArrayName + formGroupName bind array items in templates
- 4. Validation can be per-item or array-level
Interview Tips
- • Explain when to use FormArray vs a single FormGroup
- • Be ready to describe the template binding pattern
- • Mention array-level validators for minimum items
Cheat Sheet
FormArray Cheat Sheet
Create:
this.fb.array([this.createItem()])
Add/remove:
this.items.push(this.createItem());
this.items.removeAt(index);
Template:
<div formArrayName="items">
@for (item of items.controls; track item; let i = $index) {
<div [formGroupName]="i">
<input formControlName="product">
</div>
}
</div>