Implementing Dynamic Forms with Directives in Angular allows us to create flexible, reusable, and scalable forms. This guide will walk you through the step-by-step process of using directives to build a dynamic form system that can adapt to different form structures.
Step 1: Understanding Dynamic Forms
A dynamic form allows users to define form fields dynamically at runtime. It is useful when form fields need to change based on user input or external configurations.
Features of Our Dynamic Form System
- Uses Directives to create reusable form field components.
- Form fields are defined dynamically using JSON configurations.
- Supports different input types (text, dropdown, checkbox, etc.).
- Uses Reactive Forms for validation and data handling.
Step 2: Setting Up the Angular Project
Ensure you have Angular CLI installed. If not, install it:
npm install -g @angular/cli
Now, create a new Angular project:
ng new dynamic-forms
cd dynamic-forms
ng serve
Step 3: Creating the Form Directive
Create a directive that will act as a placeholder for dynamically injected form controls.
ng generate directive directives/dynamic-field
Modify the dynamic-field.directive.ts
file:
dynamic-field.directive.ts
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appDynamicField]',
})
export class DynamicFieldDirective {
constructor(public viewContainerRef: ViewContainerRef) {}
}
Explanation:
ViewContainerRef
is used to inject components dynamically.- This directive will be placed where dynamic form controls should be rendered.
Step 4: Creating a Form Control Factory
Create a service that dynamically loads different form controls.
ng generate service services/form
Modify form.service.ts
:
form.service.ts
import { Injectable, ComponentFactoryResolver, ViewContainerRef, Type } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class FormService {
private viewContainerRef!: ViewContainerRef;
setViewContainerRef(viewContainerRef: ViewContainerRef) {
this.viewContainerRef = viewContainerRef;
}
loadComponent(component: Type<any>) {
this.viewContainerRef.clear();
this.viewContainerRef.createComponent(component);
}
}
Explanation:
setViewContainerRef(viewContainerRef)
: Stores reference of the container.loadComponent(component)
: Dynamically loads the selected form field component.
Step 5: Creating Dynamic Form Components
Each field type will be a separate component.
ng generate component components/fields/input
ng generate component components/fields/checkbox
ng generate component components/fields/select
Input Field
Modify input.component.html
:
<div>
<label>{{ field.label }}</label>
<input [formControl]="control" [type]="field.type" />
</div>
Modify input.component.ts
:
import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-input-field',
templateUrl: './input.component.html',
})
export class InputComponent {
@Input() field!: any;
@Input() control!: FormControl;
}
Checkbox Field
Modify checkbox.component.html
:
<div>
<label>
<input type="checkbox" [formControl]="control" />
{{ field.label }}
</label>
</div>
Modify checkbox.component.ts
:
import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-checkbox-field',
templateUrl: './checkbox.component.html',
})
export class CheckboxComponent {
@Input() field!: any;
@Input() control!: FormControl;
}
Select Field
Modify select.component.html
:
<div>
<label>{{ field.label }}</label>
<select [formControl]="control">
<option *ngFor="let option of field.options" [value]="option">{{ option }}</option>
</select>
</div>
Modify select.component.ts
:
import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-select-field',
templateUrl: './select.component.html',
})
export class SelectComponent {
@Input() field!: any;
@Input() control!: FormControl;
}
Step 6: Creating the Dynamic Form Container
Create a component to manage dynamic form generation.
ng generate component components/dynamic-form
Modify dynamic-form.component.html
:
<form [formGroup]="form">
<ng-container *ngFor="let field of fields">
<ng-container appDynamicField></ng-container>
</ng-container>
<button type="submit" (click)="onSubmit()">Submit</button>
</form>
Modify dynamic-form.component.ts
:
import { Component, Input, ViewChild, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { DynamicFieldDirective } from '../../directives/dynamic-field.directive';
import { FormService } from '../../services/form.service';
import { InputComponent } from '../fields/input/input.component';
import { CheckboxComponent } from '../fields/checkbox/checkbox.component';
import { SelectComponent } from '../fields/select/select.component';
@Component({
selector: 'app-dynamic-form',
templateUrl: './dynamic-form.component.html',
})
export class DynamicFormComponent implements OnInit {
@Input() fields: any[] = [];
form!: FormGroup;
@ViewChild(DynamicFieldDirective, { static: true }) dynamicField!: DynamicFieldDirective;
constructor(private formService: FormService) {}
ngOnInit() {
this.form = new FormGroup({});
this.fields.forEach((field) => {
this.form.addControl(field.name, new FormControl(''));
});
this.formService.setViewContainerRef(this.dynamicField.viewContainerRef);
}
loadFieldComponent(field: any) {
let component;
if (field.type === 'text' || field.type === 'email') {
component = InputComponent;
} else if (field.type === 'checkbox') {
component = CheckboxComponent;
} else if (field.type === 'select') {
component = SelectComponent;
}
if (component) {
const instance = this.formService.loadComponent(component);
instance.instance.field = field;
instance.instance.control = this.form.get(field.name);
}
}
onSubmit() {
console.log(this.form.value);
}
}
Explanation:
- Uses
ViewChild
to inject the directive. - Initializes the form dynamically based on the provided fields.
- Uses the service to load appropriate components dynamically.
- Prints form values on submission.
Step 7: Using Dynamic Form in App Component
Modify app.component.html
:
<app-dynamic-form [fields]="formFields"></app-dynamic-form>
Modify app.component.ts
:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
formFields = [
{ name: 'username', label: 'Username', type: 'text' },
{ name: 'email', label: 'Email', type: 'email' },
{ name: 'subscribe', label: 'Subscribe', type: 'checkbox' },
{ name: 'gender', label: 'Gender', type: 'select', options: ['Male', 'Female', 'Other'] },
];
}
Step 8: Running the Application
Run the application using:
ng serve
Now, you have a dynamic form that adjusts based on the provided configuration.