⚡️Understanding angular dynamic forms with JSON data
Tags: angular, forms, webdev, javascript
Published: 2023-11-17 05:08:11

Angular is a powerful and popular front-end framework that allows developers to build dynamic and interactive web applications. One of Angular's key features is its ability to seamlessly process modules. In this blog post, we will explore the concept of dynamic forms in Angular and explore how JSON can be utilized to create dynamic and flexible forms.

## What are Dynamic Forms?

Dynamic forms in Angular are forms that are generated dynamically at runtime based on certain conditions or data. Unlike static forms, where the structure is predefined in the HTML, dynamic forms are more flexible and can adapt to changing requirements. This is particularly useful in scenarios where the form structure needs to be determined dynamically based on user input, API responses, or other dynamic data sources.

## Creating a Simple Dynamic Form

Let's walk through a basic example of creating a dynamic form using Angular and JSON.

**Step 1: Set Up Your Angular Project**
Assuming you have Angular CLI installed, create a new Angular project:

```bash
ng new dynamic-forms
cd dynamic-forms
```
**Step 2: Install Required Dependencies**
Install the Angular Reactive Forms module, which will be used to create and manage dynamic forms:

```bash
ng generate module dynamic-form
```

**Step 3: Define interface**
Create a file named `form.interface.ts` to define the structure of your dynamic form interface:

```typescript
export interface IFormStructure {
  type: string;
  label: string;
  name: string;
  value: string | number | boolean;
  options?: { label: string; value: number | string | boolean }[];
  validations?: {
    name: string;
    validator: string;
    message: string;
  }[];
}
```
**Step 4: Define Form Structure in JSON**
Create a file named `form-config.ts` to define the structure of your dynamic form:

```typescript
import { IFormStructure } from './form.interface';

export const formConfig: IFormStructure[] = [
  {
    type: 'text',
    label: 'Name',
    name: 'name',
    value: '',
    validations: [
      {
        name: 'required',
        validator: 'required',
        message: 'Name is required',
      },
    ],
  },
  {
    type: 'textarea',
    label: 'Description',
    name: 'description',
    value: '',
    validations: [
      {
        name: 'required',
        validator: 'required',
        message: 'Description is required',
      },
    ],
  },
  {
    type: 'number',
    label: 'Age',
    name: 'age',
    value: '',
    validations: [],
  },
  {
    type: 'radio',
    label: 'Gender',
    name: 'gender',
    value: true,
    options: [
      { label: 'Male', value: true },
      { label: 'Female', value: false },
    ],
    validations: [],
  },
  {
    type: 'select',
    label: 'Country',
    name: 'country',
    value: 1,
    options: [
      { label: 'India', value: 1 },
      { label: 'USA', value: 2 },
      { label: 'Canada', value: 3 },
    ],
    validations: [
      {
        name: 'required',
        validator: 'required',
        message: 'Address is required',
      },
    ],
  },
];
```
**Step 5: Create Dynamic Form Component**
Generate a new component to handle the dynamic form:

```bash
ng generate component dynamic-form
```

**Step 6: Dynamically Generate Form**
Update the `dynamic-form.component.ts` file to dynamically generate the form based on the JSON configuration:

```typescript
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { IFormStructure } from './form.interface';
import { formConfig } from './form-config';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.scss'],
})
export class DynamicFormComponent implements OnInit {
  formStructure: IFormStructure[] = formConfig;

  dynamicForm: FormGroup = this.fb.group({});

  constructor(private fb: FormBuilder) {
    let formGroup: Record<string, any> = {};
    this.formStructure.forEach((control) => {
      let controlValidators: Validators[] = [];

      if (control.validations) {
        ``;
        control.validations.forEach(
          (validation: {
            name: string;
            validator: string;
            message: string;
          }) => {
            if (validation.validator === 'required')
              controlValidators.push(Validators.required);
            if (validation.validator === 'email')
              controlValidators.push(Validators.email);
            // Add more built-in validators as needed
          }
        );
      }

      formGroup[control.name] = [control.value || '', controlValidators];
    });

    this.dynamicForm = this.fb.group(formGroup);
  }

  getErrorMessage(control: any) {
    const formControl = this.dynamicForm.get(control.name);

    if (!formControl) {
      return '';
    }

    for (let validation of control.validations) {
      if (formControl.hasError(validation.name)) {
        return validation.message;
      }
    }

    return '';
  }

  onSubmit() {
    if (!this.dynamicForm.valid) {
      this.dynamicForm.markAllAsTouched();
      return;
    }
    console.log(this.dynamicForm.value);
  }
}
```

Step 7: Import `bootstrap.css` in `style.css/scss` file

```css
@import "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css";
```

**Step 8: Display the Form in the Template**
Update the `dynamic-form.component.html` file to display the dynamic form:

```html
<div class="card">
  <div class="card-body">
    <form [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
      <div class="row">
        <div *ngFor="let control of formStructure" class="col-3">
          <!-- Text, Textarea & Number -->
          <div
            class="mb-3"
            *ngIf="['text', 'number', 'textarea'].includes(control.type)"
          >
            <label for="exampleFormControlInput1" class="form-label">
              {{ control.label }}
            </label>
            <input
              [type]="control.type"
              class="form-control"
              [formControlName]="control.name"
              *ngIf="control.type !== 'textarea'"
            />
            <textarea
              class="form-control"
              [formControlName]="control.name"
              *ngIf="control.type === 'textarea'"
            >
            </textarea>
            <span
              class="error"
              *ngIf="
                dynamicForm.controls[control.name]?.invalid &&
                dynamicForm.controls[control.name]?.touched
              "
            >
              {{ getErrorMessage(control) }}
            </span>
          </div>

          <!-- Radio Button -->
          <div class="mb-3" *ngIf="['radio'].includes(control.type)">
            <div>
              <label for="exampleFormControlInput1" class="form-label">
                {{ control.label }}
              </label>
            </div>
            <div
              class="form-check form-check-inline"
              *ngFor="let option of control.options"
            >
              <input
                class="form-check-input"
                type="radio"
                [formControlName]="control.name"
                id="inlineRadio1"
                [value]="option.value"
              />
              <label class="form-check-label" for="inlineRadio1">
                {{ option.label }}</label
              >
            </div>
          </div>

          <!-- Select  -->
          <div class="mb-3" *ngIf="['select'].includes(control.type)">
            <label for="exampleFormControlInput1" class="form-label">
              {{ control.label }}
            </label>
            <select
              class="form-select"
              aria-label="Default select example"
              [formControlName]="control.name"
            >
              <option selected>-Select-</option>
              <option
                *ngFor="let option of control.options"
                [value]="option.value"
              >
                {{ option.label }}
              </option>
            </select>
            <span
              class="error"
              *ngIf="
                dynamicForm.controls[control.name]?.invalid &&
                dynamicForm.controls[control.name]?.touched
              "
            >
              {{ getErrorMessage(control) }}
            </span>
          </div>
        </div>
      </div>

      <button type="submit" class="btn btn-primary">Submit</button>
    </form>
  </div>
</div>

```
**Step 9: Integrate the Dynamic Form Component**
Integrate the DynamicFormComponent into your main application by adding it to the `app.component.html` file or any other desired location.

```html
<app-dynamic-form></app-dynamic-form>
```
**Step 10: Run Your Angular Application**
Run your Angular application to see the dynamically generated form in action:

```bash
ng serve
```
Visit `http://localhost:4200/` in your browser to interact with the dynamic form.

**Output**

![dynamicform](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1omp4qvsvw5023f4omg7.png)




## Conclusion
In this blog post, we explored the concept of dynamic forms in Angular and how JSON can be leveraged to define the structure of these forms. By using Angular Reactive Forms and dynamically generating form controls based on JSON configuration, developers can create flexible and adaptive forms that cater to diverse requirements. This approach enhances maintainability and makes it easier to handle dynamic form scenarios in Angular applications.