Dynamic Nested Reactive Forms In Angular

When A Normal Form Won’t Cut It.

Form

Form Group

Form Array

Form Control

Form Layout Illustration
// team.component.tsteamForm: FormGroup;
teamFormSub: Subscription;
players: FormArray;
constructor(private teamFormService: TeamFormService) { }ngOnInit() {
this.teamFormSub = this.teamFormService.teamForm$
.subscribe(team => {
this.teamForm = team;
this.players = this.teamForm.get('players') as FormArray;
})
}

Handling CRUD operations from a single point

// team-form.service.tsimport { Injectable } from '@angular/core'
import { Observable, BehaviorSubject } from 'rxjs'
import { FormGroup, FormBuilder, FormArray, Validators } from '@angular/forms'
import { TeamForm, Team } from './_models'import { PlayerForm, Player } from './player'
@Injectable()
export class TeamFormService {
private teamForm: BehaviorSubject<FormGroup | undefined> =
new BehaviorSubject(this.fb.group(
new TeamForm(new Team('Cavaliers'))
))
teamForm$: Observable<FormGroup> = this.teamForm.asObservable() constructor(private fb: FormBuilder) {} addPlayer() {
const currentTeam = this.teamForm.getValue()
const currentPlayers = currentTeam.get('players') as FormArray
currentPlayers.push(
this.fb.group(
new PlayerForm(new Player('', '', 0, ''))
)
)
this.teamForm.next(currentTeam)
}
deletePlayer(i: number) {
const currentTeam = this.teamForm.getValue()
const currentPlayers = currentTeam.get('players') as FormArray
currentPlayers.removeAt(i)
this.teamForm.next(currentTeam)
}
} // end class
// team-form.model.tsexport class TeamForm {
name = new FormControl()
players = new FormArray([])

constructor(team: Team) {
if (team.name) {
this.name.setValue(team.name)
}
if (team.players) {
this.players.setValue([team.players])
}
}
}

Dynamically insert and remove child forms in/from a parent form

// team-form-service.tsaddPlayer() {
const currentTeam = this.teamForm.getValue()
const currentPlayers = currentTeam.get('players') as FormArray
currentPlayers.push(
this.fb.group(
new PlayerForm(new Player('', '', 0, ''))
)
)
this.teamForm.next(currentTeam)
}
// team.component.tsaddPlayer() {
this.teamFormService.addPlayer()
}
deletePlayer(index: number){
this.teamFormService.deletePlayer(index)
}
// player-form.model.tsimport { FormControl, Validators } from '@angular/forms'
import { Player } from './player.model'
export class PlayerForm {
firstName = new FormControl()
lastName = new FormControl()
position = new FormControl()
number = new FormControl()
constructor(
player: Player
) {
this.firstName.setValue(player.firstName)
this.firstName.setValidators([Validators.required])
this.lastName.setValue(player.lastName)
this.position.setValue(player.position)
this.number.setValue(player.number)
this.number.setValidators([Validators.required])
}
}

Composing Validation Statuses

Parent Form : status
> child form : status
> child form : status
Parent Form : status (Valid)
> child form : status (Valid)
> child form : status (Valid)
Parent Form : status (Invalid)
> child form : status (Valid)
> child form : status (Invalid)

Share a single parent form for submitting

Final submitted form result

Software engineer, writer, traveler, weight lifter

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store