Chapter 8.2โ˜• 15 min read

FormControl, FormGroup, FormArray

Three building blocks of reactive forms: single input (FormControl), object group (FormGroup), and dynamic array (FormArray). Combine them for any form structure.

01FormControl โ€” Single Input

FormControl is the basic building block โ€” it tracks the value and validation state of a single input field.

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

// FormControl(initialValue, validators)
const name = new FormControl('Biryani', [
  Validators.required,
  Validators.minLength(3)
]);

// Properties
name.value        // 'Biryani'
name.valid        // true (passes required and minLength)
name.errors       // null (no errors)
name.setValue('Chicken Biryani');  // Programmatically set

"FormControl = ek plate โ€” ek dish ka data."

Every FormControl holds:

  • A current value
  • Validation state (valid/invalid/errors)
  • Interaction state (touched/dirty/pristine)
  • An Observable of changes (valueChanges)
02FormGroup โ€” Collection of Controls

FormGroup groups multiple FormControls into one object โ€” like a form with multiple fields.

import { FormGroup, FormControl, Validators } from '@angular/forms';

const biryaniForm = new FormGroup({
  name: new FormControl('', [Validators.required]),
  price: new FormControl(0, [Validators.min(1)]),
  category: new FormControl('chicken'),
});

"FormGroup = full thaali โ€” multiple plates ek saath."

Template:

<form [formGroup]="biryaniForm">
  <input formControlName="name" placeholder="Biryani name" />
  <input type="number" formControlName="price" />
  <select formControlName="category">
    <option value="chicken">Chicken</option>
    <option value="mutton">Mutton</option>
  </select>
</form>

formGroup binds the FormGroup to the form element. formControlName binds each input to a control by its key name.

biryaniForm.value   // { name: '', price: 0, category: 'chicken' }
biryaniForm.valid   // true only if ALL controls are valid
03FormArray โ€” Dynamic List of Controls

FormArray manages a dynamic list of controls โ€” like adding multiple side orders.

import { FormArray, FormControl } from '@angular/forms';

const extras = new FormArray([
  new FormControl('Raita'),
  new FormControl('Salan'),
]);

"FormArray = side orders โ€” jitne chahiye utne add karo."

Adding and removing dynamically:

// Add a new control
extras.push(new FormControl('Mirchi'));

// Remove at index
extras.removeAt(0);

// Clear all
extras.clear();

// Insert at position
extras.insert(1, new FormControl('Salad'));

// Get length
extras.length; // 3 (after push)

Template:

<div formArrayName="extras">
  @for (item of extras.controls; track $index) {
    <div>
      <input [formControl]="item" />
      <button (click)="extras.removeAt($index)">Remove</button>
    </div>
  }
</div>

Use cases: multiple phone numbers, dynamic tags, order items, team members.

04Nested Structure

You can nest FormGroups inside FormGroups and FormArrays inside FormGroups โ€” creating complex form structures.

const orderForm = new FormGroup({
  customerName: new FormControl(''),
  
  // Nested FormGroup
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl('Hyderabad'),
    pincode: new FormControl(''),
  }),
  
  // FormArray
  orders: new FormArray([
    new FormGroup({
      item: new FormControl('', Validators.required),
      qty: new FormControl(1, [Validators.min(1)]),
    })
  ]),
});

"FormGroup ke andar FormGroup, FormArray ke andar FormGroup โ€” matryoshka."

Template:

<form [formGroup]="orderForm">
  <input formControlName="customerName" />

  <div formGroupName="address">
    <input formControlName="street" />
    <input formControlName="city" />
    <input formControlName="pincode" />
  </div>

  <div formArrayName="orders">
    @for (order of orders.controls; track $index) {
      <div [formGroupName]="$index">
        <input formControlName="item" />
        <input formControlName="qty" />
      </div>
    }
  </div>
</form>

Access nested values with dot notation: this.form.get('address.city')?.value

05Getting and Setting Values

You can read and write values programmatically.

Getting values:

// Direct property
this.biryaniForm.value.name;

// Via get() โ€” returns AbstractControl | null
this.biryaniForm.get('name')?.value;

// Nested
this.biryaniForm.get('address.city')?.value;

// FormArray item
this.biryaniForm.get('orders.0.item')?.value;

Setting values:

// Set a single control
this.biryaniForm.get('name')?.setValue('Chicken Dum Biryani');

// Partial update โ€” only specified fields change
this.biryaniForm.patchValue({
  name: 'Mutton Biryani',
  price: 399
});

// Full update โ€” ALL fields required, throws error if missing
this.biryaniForm.setValue({
  name: 'Chicken Biryani',
  price: 299,
  category: 'chicken'
});

"patchValue = partial update, setValue = full replace."

  • setValue โ€” requires ALL controls in the group, throws error if any missing
  • patchValue โ€” accepts partial object, only updates specified controls
  • reset โ€” resets to initial values (or specified defaults)

Key Takeaways

  • โœ… FormControl โ€” single input (value, validators, state)
  • โœ… FormGroup โ€” object of controls, bind via [formGroup] + formControlName
  • โœ… FormArray โ€” dynamic list, push()/removeAt()/clear() at runtime
  • โœ… Nest any combination: FormGroup โ†’ FormGroup, FormGroup โ†’ FormArray, FormArray โ†’ FormGroup
  • โœ… setValue() requires ALL fields (strict), patchValue() accepts partial (flexible)
Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase