100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Dynamic and Nested Form Groups

Build complex forms by nesting FormGroups and using FormArray to add, remove, and validate repeating or conditional sets of controls at runtime.

Forms in AngularIntermediate10 min readJul 9, 2026
Analogies

Dynamic and Nested Form Groups

Real-world forms rarely map onto a flat list of fields. An address section groups street, city, and postal code together; an 'add another item' button needs a variable number of identical control sets; a checkout form might reveal entirely new fields depending on a payment method the user selects. Angular's reactive forms model this naturally because FormGroup and FormArray are themselves AbstractControls, meaning they can be nested inside one another to any depth: a FormGroup can contain other FormGroups and FormArrays as children, and a FormArray can contain FormGroups as its elements. This composability is what lets you represent an arbitrarily complex, arbitrarily shaped piece of form state as a single tree that mirrors your data model.

🏏

Cricket analogy: Nesting FormGroups and FormArrays is like a scorecard nesting a bowler's over-by-over figures inside the innings summary, which itself nests inside the match scorecard, mirroring the real shape of how cricket data is organized.

Nesting FormGroups for structured data

When a section of your form corresponds to a nested object in your data model — like an 'address' object inside a 'user' object — nest a FormGroup inside the parent FormGroup under that key. The template then uses formGroupName to bind the nested group without re-declaring formGroup, and Angular automatically scopes validation, dirty/touched state, and value aggregation to that sub-tree. This means form.get('address.city') resolves through the nested structure, and form.value naturally produces a nested object shape matching your model.

🏏

Cricket analogy: Using formGroupName for a nested 'address' group is like a scorecard app scoping 'strike rate' calculations to just the powerplay overs sub-section, so form.get('address.city') is like querying stats.get('powerplay.strikeRate').

typescript
import { FormBuilder, Validators } from '@angular/forms';

constructor(private fb: FormBuilder) {}

form = this.fb.group({
  name: ['', Validators.required],
  address: this.fb.group({
    street: ['', Validators.required],
    city: ['', Validators.required],
    postalCode: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]],
  }),
});

// Template:
// <form [formGroup]="form">
//   <input formControlName="name" />
//   <div formGroupName="address">
//     <input formControlName="street" />
//     <input formControlName="city" />
//     <input formControlName="postalCode" />
//   </div>
// </form>

FormArray for repeating control sets

FormArray manages an ordered, dynamic list of AbstractControls, which is the right tool whenever the number of fields isn't known ahead of time — line items on an invoice, phone numbers on a contact, or skills on a resume. You push new FormGroup instances onto the array with push() and remove them by index with removeAt(), and Angular re-indexes and re-validates the array automatically. In the template, iterate the array's controls with the new @for control-flow block, binding each item with formGroupName set to its index.

🏏

Cricket analogy: FormArray for invoice line items is like a bowling figures table where you push() a new over's stats after each over bowled and removeAt() a no-ball entry that gets overturned, with the table automatically re-indexing.

typescript
get skills() {
  return this.form.get('skills') as FormArray;
}

addSkill(): void {
  this.skills.push(
    this.fb.group({
      name: ['', Validators.required],
      years: [0, [Validators.required, Validators.min(0)]],
    })
  );
}

removeSkill(index: number): void {
  this.skills.removeAt(index);
}

// Template:
// <div formArrayName="skills">
//   @for (skill of skills.controls; track skill; let i = $index) {
//     <div [formGroupName]="i">
//       <input formControlName="name" />
//       <input type="number" formControlName="years" />
//       <button type="button" (click)="removeSkill(i)">Remove</button>
//     </div>
//   }
// </div>

Conditionally adding and removing controls

Sometimes a control should exist only under certain conditions — for example, a 'company name' field that only applies when the user selects 'Business' as an account type. Rather than hiding the field with CSS while leaving it in the form tree (which would submit a stale or empty value and could still be validated), add or remove the control from the FormGroup itself using addControl(), removeControl(), or setControl(), typically driven from a valueChanges subscription on the controlling field. This keeps form.value and form.valid accurately reflecting only the fields that are actually relevant to the current state.

🏏

Cricket analogy: Adding/removing a control with addControl() based on account type is like only adding a 'DRS reviews remaining' field to the scoreboard when the format is a T20 international, not permanently displaying it for a Test match scorecard.

FormArray tracks controls by position, not by identity, so if you insert or remove an item in the middle of the array, every subsequent control's index-based binding shifts. If you need stable per-item identity (e.g. for animations or trackBy-style logic), store an id inside each nested FormGroup's value rather than relying on array position.

A frequent bug is calling patchValue or setValue on a FormArray with a different length than the current array — setValue throws if lengths mismatch, and patchValue silently ignores extra or missing entries. When repopulating a FormArray from server data, clear it and rebuild it (or use a helper that resizes it) before patching values in.

  • FormGroup and FormArray are both AbstractControls and can be nested inside each other to any depth.
  • formGroupName scopes a template section to a nested FormGroup without needing a new [formGroup] binding.
  • FormArray is the right structure for a variable-length, ordered list of similarly-shaped controls.
  • Use push()/removeAt() to mutate a FormArray, and formArrayName plus an index-based formGroupName in the template.
  • Add or remove controls dynamically with addControl/removeControl so form.value and form.valid reflect only relevant fields.
  • FormArray indices shift on insert/remove, so store a stable id in the control's value if you need identity across reorders.

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#AngularStudyNotes#WebDevelopment#DynamicAndNestedFormGroups#Dynamic#Nested#Form#Groups#StudyNotes#SkillVeris