Implementing dynamic forms with directives

Loading

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

  1. Uses Directives to create reusable form field components.
  2. Form fields are defined dynamically using JSON configurations.
  3. Supports different input types (text, dropdown, checkbox, etc.).
  4. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *