Angular  

Dynamic Form Generation in Angular Using JSON Configuration

In complex enterprise applications, form requirements often vary across modules or user roles. Instead of creating multiple hard-coded forms, a scalable approach is to generate forms dynamically from a configuration object. In this article, we will walk through building a fully dynamic form system in Angular using ReactiveFormsModule and a JSON schema-based configuration.

Use of Dynamic Forms

  • Reusability: One component can handle different forms based on configuration.
  • Maintainability: Changes to the form layout/fields require only updates to the JSON config.
  • Customizability: Easily switch between form versions for different user roles or workflows.

Project Setup

Create a new Angular project.

ng new dynamic-form-demo
cd dynamic-form-demo
ng add @angular/forms

Create a folder structure like.

src/
  app/
    dynamic-form/
      dynamic-form.component.ts
      dynamic-form.component.html
      form-field.model.ts
    app.component.ts

Step 1. Define JSON Configuration and Data Model

Create a form-field.model.ts interface.

export interface FormField {
  type: string;
  label: string;
  name: string;
  placeholder?: string;
  value?: any;
  required?: boolean;
  options?: { key: string; value: string }[];
}

Example JSON configuration

this.formConfig: FormField[] = [
  {
    type: 'text',
    label: 'First Name',
    name: 'firstName',
    placeholder: 'Enter your first name',
    required: true
  },
  {
    type: 'text',
    label: 'Last Name',
    name: 'lastName',
    placeholder: 'Enter your last name'
  },
  {
    type: 'email',
    label: 'Email',
    name: 'email',
    required: true
  },
  {
    type: 'select',
    label: 'Gender',
    name: 'gender',
    options: [
      { key: 'M', value: 'Male' },
      { key: 'F', value: 'Female' }
    ]
  }
];

Step 2. Generate Form Controls Dynamically

Inside dynamic-form.component.ts.

import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FormField } from './form-field.model';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
})
export class DynamicFormComponent implements OnInit {
  @Input() fields: FormField[] = [];
  form!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    this.form = this.fb.group({});

    for (const field of this.fields) {
      const validators = field.required ? [Validators.required] : [];
      this.form.addControl(field.name, this.fb.control(field.value || '', validators));
    }
  }

  onSubmit(): void {
    if (this.form.valid) {
      console.log('Form submitted:', this.form.value);
    } else {
      console.warn('Form is invalid!');
    }
  }
}

Step 3. Create Dynamic Template

Inside dynamic-form.component.html.

<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <div *ngFor="let field of fields" class="form-group">
    <label [for]="field.name">{{ field.label }}</label>

    <input
      *ngIf="field.type !== 'select'"
      [type]="field.type"
      [formControlName]="field.name"
      [id]="field.name"
      [placeholder]="field.placeholder"
      class="form-control"
    />

    <select
      *ngIf="field.type === 'select'"
      [formControlName]="field.name"
      [id]="field.name"
      class="form-control"
    >
      <option *ngFor="let option of field.options" [value]="option.key">
        {{ option.value }}
      </option>
    </select>

    <div
      *ngIf="form.controls[field.name].invalid && form.controls[field.name].touched"
      class="text-danger"
    >
      {{ field.label }} is required.
    </div>
  </div>

  <button type="submit" class="btn btn-primary" [disabled]="form.invalid">
    Submit
  </button>
</form>

Step 4. Use the Component in AppComponent

// app.component.ts

import { Component } from '@angular/core';
import { FormField } from './dynamic-form/form-field.model';

@Component({
  selector: 'app-root',
  template: `<app-dynamic-form [fields]="formConfig"></app-dynamic-form>`,
})
export class AppComponent {
  formConfig: FormField[] = [
    { type: 'text', label: 'Name', name: 'name', required: true },
    { type: 'email', label: 'Email', name: 'email', required: true },
    {
      type: 'select',
      label: 'Role',
      name: 'role',
      options: [
        { key: 'admin', value: 'Administrator' },
        { key: 'user', value: 'User' },
      ],
    },
  ];
}

Styling

form {
  max-width: 600px;
  margin: auto;
}

.form-group {
  margin-bottom: 15px;
}

.text-danger {
  font-size: 0.9em;
}

Conclusion

Dynamic forms in Angular offer a powerful abstraction for rendering user interfaces based on configuration rather than hardcoded templates. This approach increases productivity, simplifies maintenance, and supports customization across projects.

You can even extend this into a low-code form engine or integrate with CMS-like systems to allow non-devs to define form structures.