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.