Angular 2+ Reactive Form - Adding & Removing Form Controls
How to dynamically add and remove FormControls and FormGroups from FormArrays and FormGroups in Angular.
May 17, 2018
Complicated Problem 5: I need to dynamically add and remove form controls & form groups
Ok, let’s pretend we have an interface like so:
First we create our nested form.
this.recipeForm = this.fb.group({
name: [null, Validators.required],
description: [null],
source: [null],
category: [null],
subcategory: [[]],
//notice how we're passing a formbuilder array here
ingredients: this.fb.array([]),
calories: [null, [Validators.required, this.amountValidator]]
});
Next we’ll write the function to create a new nested form groups
public addIngredient() {
const ingredientsFormArray = this.recipeForm.controls['ingredients'];
//just your regular ole form group here, nothing new.
const ingredientFormGroup = new FormGroup({
name: new FormControl(null, [Validators.required]),
amount: new FormControl(null, [Validators.required])
});
ingredientsFormArray.push(ingredientFormGroup);
}
We’ll also want to be able to remove dynamic form groups as well.
public removeIngredient(i) {
const ingredientsFormArray = this.recipeForm.controls['ingredients'];
ingredientsFormArray.removeAt(i);
}
When creating the markup, the most important thing with nested forms is remembering to pass the formgroup iteratee as the formgroup model.
<label for="ingredients">Ingredients</label>
<ng-container *ngFor="let ingredient of recipeForm.controls['ingredients'].controls; let i = index" >
//passing our ingredient iteratee as our formgroup here
<div [formGroup]="ingredient" class="row">
//don't you forget about me!
<div class="form-group col-sm">
<label for="name">Name</label>
<input class="form-control" formControlName="name" aria-describedby="name">
</div>
<div class="form-group col-sm">
<label for="amount">Amount</label>
<input class="form-control" formControlName="amount" aria-describedby="amount">
</div>
<div class="col-sm">
<button class="btn btn-danger remove-ingredient" (click)="removeIngredient(i)">
<fa-icon icon="times"></fa-icon>
</button>
</div>
</div>
</ng-container>
<div class="row">
<div class="col-sm">
<button type="button" class="btn btn-success" (click)="addIngredient()">Add ingredient</button>
</div>
</div>
But howtf do I validate dynamic forms???
Fear not, my young padawan, we can still iterate through our form controls and nested form arrays.
Object.keys(this.recipeForm.controls).forEach(field => {
const control = this.recipeForm.get(field);
if (!control['controls']) {
control.markAsTouched({ onlySelf: true });
}
else {
let nestedFormArray = control['controls'];
nestedFormArray.forEach(subcCtrlGp => {
Object.keys(subcCtrlGp['controls']).forEach(subField => {
const nestedControl = subcCtrlGp.get(subField);
nestedControl.markAsTouched({ onlySelf: true });
});
});
//can extract this to a recursive function for deeply nested forms
}
})
Demo code available at https://github.com/tehfedaykin/complicated-forms-app