Chapter 8.7☕ 15 min read

Dynamic Forms — Add/Remove Controls at Runtime

The power of reactive forms: add and remove fields at runtime. FormArray for dynamic lists, addControl/removeControl for dynamic groups. Template updates automatically.

01What are Dynamic Forms

Dynamic forms change their structure at runtime based on user actions, API responses, or application state.

"Dynamic form — menu badalta hai order ke hisaab se."

Examples of dynamic forms in real apps:

  • Order form: User clicks "Add Item" — a new row appears with name, qty, price fields
  • Survey builder: Admin adds questions dynamically — each with different input types
  • Team management: Add/remove team members with their roles
  • Multiple addresses: User can add multiple delivery addresses
  • Dynamic filters: E-commerce filter panel with dynamic filter options

This is the superpower of reactive forms. Template-driven forms CANNOT do this without complex DOM manipulation. Reactive forms handle it natively with FormArray.

02Adding Controls Dynamically

Adding controls dynamically works on both FormGroup and FormArray.

Add to FormGroup:

private fb = inject(FormBuilder);

// Start with a simple form
form = this.fb.group({
  name: [''],
  price: [0],
});

// Add a new control later
addDiscountField() {
  this.form.addControl('discount', this.fb.control(0, [Validators.min(0), Validators.max(100)]));
}

// Template auto-updates — no manual DOM changes!
// Just add: 

Add to FormArray:

// Start with empty FormArray
items = this.fb.array([]);

// Add items dynamically
addItem() {
  const itemGroup = this.fb.group({
    name: ['', Validators.required],
    qty: [1, [Validators.required, Validators.min(1)]],
    price: [0, [Validators.required, Validators.min(0)]],
  });
  this.items.push(itemGroup);
}

"Push karo FormArray mein — template automatically naya field dikhayega."

No need to manipulate the DOM manually. FormArray.push() adds a control, and the template @for loop automatically picks up the new item.

03Removing Controls Dynamically

Removing controls is just as easy as adding them.

Remove from FormGroup:

// Remove a control
this.form.removeControl('discount');

// Check if a control exists before removing
if (this.form.contains('discount')) {
  this.form.removeControl('discount');
}

Remove from FormArray:

// Remove at specific index
this.items.removeAt(index);

// Remove first item
this.items.removeAt(0);

// Remove last item
this.items.removeAt(this.items.length - 1);

// Clear all items
this.items.clear();

// Keep at least one item (prevent empty form)
if (this.items.length > 1) {
  this.items.removeAt(index);
}

Template — auto-updates on removal:

@for (item of items.controls; track $index) {
  <div [formGroupName]="$index">
    <input formControlName="name" />
    <input formControlName="qty" type="number" />
    <button (click)="removeItem($index)">✕ Remove</button>
  </div>
}

"Remove karo — template automatically hata dega."

When you call removeAt(), the FormArray shrinks, and the @for loop automatically reflects this — no manual DOM updates needed.

04Complete Dynamic Form Example — Order Items

Let's build a complete dynamic order form — the most common use case.

import { Component, inject } from '@angular/core';
import { FormBuilder, Validators, FormArray, ReactiveFormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

@Component({
  selector: 'app-dynamic-order',
  standalone: true,
  imports: [ReactiveFormsModule, JsonPipe],
  template: `
    <form [formGroup]="orderForm" (ngSubmit)="onSubmit()">
      <h2>🍗 Biryani Order</h2>

      <!-- Customer name -->
      <div class="field">
        <label>Customer Name *</label>
        <input formControlName="customerName" placeholder="Your name" />
      </div>

      <!-- Dynamic Order Items -->
      <div formArrayName="items" class="items-section">
        <h3>Order Items</h3>

        @for (item of items.controls; track $index) {
          <div [formGroupName]="$index" class="order-item">
            <span class="item-num">#{{ $index + 1 }}</span>
            <input formControlName="name" placeholder="Biryani name" />
            <input formControlName="qty" type="number" min="1" class="qty" />
            <input formControlName="price" type="number" min="0" class="price" />
            <button type="button" (click)="removeItem($index)" 
                    [disabled]="items.length <= 1" class="remove-btn">✕</button>
          </div>
        }

        <button type="button" (click)="addItem()" class="add-btn">
          + Add Item
        </button>
      </div>

      <!-- Summary -->
      <div class="summary">
        <p>Total items: {{ items.length }}</p>
        <p>Total: ₹{{ getTotal() }}</p>
      </div>

      <button type="submit" [disabled]="orderForm.invalid" class="submit-btn">
        Place Order 🚀
      </button>
    </form>
  `
})
export class DynamicOrderComponent {
  private fb = inject(FormBuilder);

  orderForm = this.fb.group({
    customerName: ['', Validators.required],
    items: this.fb.array([]),
  });

  get items(): FormArray {
    return this.orderForm.get('items') as FormArray;
  }

  constructor() {
    this.addItem(); // Start with one item
  }

  addItem() {
    this.items.push(this.fb.group({
      name: ['', Validators.required],
      qty: [1, [Validators.required, Validators.min(1)]],
      price: [0, [Validators.required, Validators.min(0)]],
    }));
  }

  removeItem(index: number) {
    if (this.items.length > 1) {
      this.items.removeAt(index);
    }
  }

  getTotal(): number {
    return this.items.controls.reduce((total, group) => {
      const price = group.get('price')?.value || 0;
      const qty = group.get('qty')?.value || 0;
      return total + (price * qty);
    }, 0);
  }

  onSubmit() {
    if (this.orderForm.valid) {
      console.log('Order:', this.orderForm.value);
    }
  }
}

"Order items = jitne chahiye utne add karo, hatao bhi."

This pattern is used in every real-world Angular app that handles orders, invoices, surveys, or any dynamic list.

05Form Configuration from API (Advanced Pattern)

For advanced apps, the form structure itself can come from an API — not hardcoded.

// API returns form configuration
[
  { key: 'name', type: 'text', label: 'Name', validators: ['required'] },
  { key: 'email', type: 'email', label: 'Email', validators: ['required', 'email'] },
  { key: 'age', type: 'number', label: 'Age', validators: ['min:18', 'max:99'] },
  { key: 'country', type: 'select', label: 'Country', 
    options: [{ value: 'IN', label: 'India' }, { value: 'US', label: 'USA' }],
    validators: ['required'] },
]
// Build form dynamically from config
buildForm(config: FormFieldConfig[]): FormGroup {
  const group: any = {};

  config.forEach(field => {
    const validators = this.mapValidators(field.validators);
    group[field.key] = this.fb.control('', validators);
  });

  return this.fb.group(group);
}

private mapValidators(validatorNames: string[]): ValidatorFn[] {
  return validatorNames.map(name => {
    if (name === 'required') return Validators.required;
    if (name.startsWith('min:')) return Validators.min(+name.split(':')[1]);
    if (name.startsWith('max:')) return Validators.max(+name.split(':')[1]);
    if (name === 'email') return Validators.email;
    return [];
  }).flat();
}

"API se form ka blueprint aata hai — frontend build karta hai."

Use cases for API-driven forms:

  • Admin panels where form fields change based on user roles
  • Survey builders where questions are configured in a CMS
  • Multi-tenant apps where each customer has different form requirements
  • Dynamic checkout forms based on product type

This is an advanced pattern — master the basics first, then explore API-driven forms for enterprise apps.

Key Takeaways

  • ✅ Dynamic forms change structure at runtime — add/remove controls, template auto-updates
  • ✅ FormGroup: addControl(key, control) / removeControl(key) — dynamic fields
  • ✅ FormArray: push() / removeAt(index) / clear() — dynamic lists
  • ✅ Real-world pattern: FormArray of FormGroups for order items, team members, addresses
  • ✅ Advanced: API-driven forms — form config from server, build form dynamically with loop
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