Angular Dynamic Reactive Form
Configuration → Create FormGroup → Loop controls → Bind HTML → Validate → Submit
1. Requirement
Suppose the fields are not fixed. The backend/configuration gives us:
[
{ name: 'firstName', type: 'text', label: 'First Name', required: true },
{ name: 'email', type: 'email', label: 'Email', required: true },
{ name: 'age', type: 'number', label: 'Age', required: false }
]Based on this configuration, Angular should dynamically create the form.
Flow
Backend / Configuration
↓
Field Configuration Array
↓
Create FormGroup dynamically
↓
Add FormControl for each field
↓
HTML *ngFor
↓
Render input dynamically
↓
Apply Validators dynamically
↓
User enters data
↓
Form Validation
↓
ngSubmit
↓
Send form.value to API
2. Create the component
import { Component, OnInit } from '@angular/core';
import {
FormBuilder,
FormGroup,
Validators
} from '@angular/forms';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent implements OnInit {
dynamicForm!: FormGroup;
fields = [
{
name: 'firstName',
label: 'First Name',
type: 'text',
required: true
},
{
name: 'email',
label: 'Email',
type: 'email',
required: true
},
{
name: 'age',
label: 'Age',
type: 'number',
required: false
}
];
constructor(private fb: FormBuilder) {}
ngOnInit(): void {
this.createForm();
}
createForm(): void {
const formGroup: any = {};
this.fields.forEach(field => {
const validators = [];
if (field.required) {
validators.push(Validators.required);
}
if (field.type === 'email') {
validators.push(Validators.email);
}
formGroup[field.name] = ['', validators];
});
this.dynamicForm = this.fb.group(formGroup);
}
submit(): void {
if (this.dynamicForm.invalid) {
this.dynamicForm.markAllAsTouched();
return;
}
console.log(this.dynamicForm.value);
}
}3. Understand the important part
This is the main dynamic logic:
this.fields.forEach(field => {
const validators = [];
if (field.required) {
validators.push(Validators.required);
}
if (field.type === 'email') {
validators.push(Validators.email);
}
formGroup[field.name] = ['', validators];
});For example, Angular receives:
{
name: 'email',
type: 'email',
required: true
}It dynamically creates:
email: ['', [
Validators.required,
Validators.email
]]So the final FormGroup becomes approximately:
FormGroup {
firstName: FormControl,
email: FormControl,
age: FormControl
}4. Create HTML dynamically
<form [formGroup]="dynamicForm" (ngSubmit)="submit()">
<div *ngFor="let field of fields">
<label>
{{ field.label }}
</label>
<input
[type]="field.type"
[formControlName]="field.name"
/>
<div
*ngIf="
dynamicForm.get(field.name)?.touched &&
dynamicForm.get(field.name)?.invalid
"
>
<span
*ngIf="dynamicForm.get(field.name)?.hasError('required')"
>
{{ field.label }} is required
</span>
<span
*ngIf="dynamicForm.get(field.name)?.hasError('email')"
>
Please enter a valid email
</span>
</div>
</div>
<button type="submit">
Submit
</button>
</form>5. How [formControlName] works dynamically
This line is very important:
[formControlName]="field.name"If:
field.name = 'firstName'Angular internally treats it like:
formControlName="firstName"For the next field:
field.name = 'email'it becomes:
formControlName="email"Therefore, we don’t need to manually write:
<input formControlName="firstName">
<input formControlName="email">
<input formControlName="age">The fields are generated dynamically.
6. Validation flow
Suppose the configuration is:
{
name: 'email',
type: 'email',
required: true
}We create:
Validators.required
Validators.emailSo if the user enters:
abcthe form is invalid because:
abc → Invalid emailIf the user enters:
abc@gmail.comthen:
Required → Pass
Email → Passand the control becomes valid.
7. Submit flow
When the user clicks Submit:
<form
[formGroup]="dynamicForm"
(ngSubmit)="submit()">Angular calls:
submit()Then:
if (this.dynamicForm.invalid) {
this.dynamicForm.markAllAsTouched();
return;
}If invalid, we don’t call the API.
Get Arunkumarvijay’s stories in your inbox
Join Medium for free to get updates from this writer.
If valid:
console.log(this.dynamicForm.value);Output:
{
"firstName": "Arun",
"email": "arun@gmail.com",
"age": 34
}This object can then be sent to the backend:
this.http.post('/api/users', this.dynamicForm.value)
.subscribe(response => {
console.log(response);
});8. Complete medium-level structure
A good real-world structure is:
DynamicFormComponent
│
├── fields configuration
│
├── createForm()
│ │
│ ├── loop fields
│ ├── create FormControl
│ ├── add validators
│ └── create FormGroup
│
├── HTML
│ │
│ ├── *ngFor
│ ├── text input
│ ├── number input
│ ├── email input
│ ├── select
│ └── checkbox
│
├── Validation
│ │
│ ├── required
│ ├── email
│ └── custom validators
│
└── submit()
│
├── check valid
├── get form.value
└── call API
One important point: if the dynamic form contains multiple rows of the same structure — for example, multiple employees, addresses, or tasks — then use FormArray instead of only FormGroup.






