DEV Community

Cover image for Part 1: Create Configuration Based Dynamic Forms in Angular
Loukas Kotas
Loukas Kotas

Posted on

Part 1: Create Configuration Based Dynamic Forms in Angular

Introduction

Hey there, fellow developers! Welcome to the first part of our series on creating dynamic forms in Angular based on configuration files. In this article, I'll guide you through the process of building a flexible and interactive form using Angular's reactive forms approach. We'll leverage a configuration model to control form fields, validators, and error messages dynamically. By the end of this article, you'll be able to:

  • Create dynamic form fields based on the configuration provided.
  • Control the form fields by adjusting the model interface to suit your needs.
  • Display separate error messages for each validation case.
  • Pass separate validators for each field in the configuration.

Code Example

Find a live code example on StackBlitz here. Feel free to explore the code and experiment with dynamic forms yourself!

Let's dive in! 🚀

Understanding the Goal

Our main objective is to develop a robust form component capable of generating dynamic form fields based on a configuration file. The cornerstone of this system is the DynamicFormFieldModel, enabling us to define essential properties for each form field:

  • id: A unique key serving to identify the dynamic form field.
  • label: Presented as a placeholder or title for the field.
  • type: Determines the type of form field component to be rendered, such as a text input or a dropdown/select.
  • selectMenuOptions (optional): Defines an array of options for the dropdown if the field is of type 'select'.
  • value (optional): Provides a default value for the form field, with an empty string used as the default if unspecified.
  • validators (optional): Empowers us to apply specific validation rules to the field using an array of Angular Validators.

With the versatile DynamicFormFieldModel, we can easily customize and extend our form fields based on the configuration, making it a potent solution for creating dynamic forms with diverse input types and validation requirements.

Feel free to add as many fields as needed; the system accommodates future scenarios, such as incorporating new components (e.g calendar). There is no limit to the fields we can add, providing flexibility and adaptability to our form-building capabilities.

Now, let's delve into the implementation details! 💻

export interface DynamicFormFieldModel {
  id: string;
  label: string;
  type: DynamicFormFieldModelType;
  selectMenuOptions?: SelectMenuOption[];
  value?: string | SelectMenuOption[];
  validators?: ValidatorFn[];
}
Enter fullscreen mode Exit fullscreen mode

Building the Dynamic Form Field Component

In this section, we will explore the dynamic-form-field.component.ts file, which serves as the cornerstone of our dynamic form fields implementation. By leveraging Angular's powerful FormGroupDirective, we gain access to the root form of the parent component that hosts our dynamic form fields. This connection allows us to seamlessly render different types of form fields based on the type property derived from the DynamicFormFieldModel provided in the configuration.

...
export class DynamicFormFieldComponent {
  @Input() formItem!: DynamicFormFieldModel;
  form!: FormGroup;

  constructor(private rootFormGroup: FormGroupDirective) {
   this.form = this.rootFormGroup.control;
  }
}
Enter fullscreen mode Exit fullscreen mode

Defining the Model

The dynamic-form-field.model.ts file houses our configuration model. Here, we define the types and interfaces for our dynamic form fields. The DynamicFormFieldModelType allows us to restrict the available field types to 'text' or 'select'.

It is important to note that if we plan to add more input elements to the dynamic-form-field.component, we must proactively update the DynamicFormFieldModelType as well. This proactive approach ensures that the model remains accurate and reflects the latest available field types, enabling seamless integration of new input elements without any compatibility issues. By maintaining consistency between the component and the model, we guarantee smooth expansion and optimization of our dynamic form-building capabilities.

export type DynamicFormFieldModelType = 'text' | 'select';

export interface SelectMenuOption {
  label: string;
  value: string;
}

export interface DynamicFormFieldModel {
  id: string;
  label: string;
  type: DynamicFormFieldModelType;
  selectMenuOptions?: SelectMenuOption[];
  value?: string | SelectMenuOption[];
  validators?: ValidatorFn[];
}
Enter fullscreen mode Exit fullscreen mode

Rendering the Dynamic Form Fields

In dynamic-form-field.component.html, we leverage Angular's ngSwitch to dynamically render the appropriate form field based on its type. We efficiently control the dynamic generation of form fields while ensuring separate error messages for each validation case. The form property, represented by a FormGroup, is initialized with the root form control of the parent component. This design enables smooth synchronization of values and validation between the parent form and the DynamicFormFieldComponent.

<div class="dynamic-form-field-container">
  <ng-container [formGroup]="form">
    <ng-container [ngSwitch]="formItem.type">
      <mat-form-field *ngSwitchCase="'text'">
        <!-- ... -->
      </mat-form-field>
      <mat-form-field *ngSwitchCase="'select'">
        <!-- ... -->
      </mat-form-field>
    </ng-container>
  </ng-container>
</div>
Enter fullscreen mode Exit fullscreen mode

Assembling the Main Application Component

In this approach, we leverage the setupFormFields function to dynamically generate form fields based on the configuration specified in the dynamicFormFields variable.

By calling this function, we iterate through each item in the configuration and create the corresponding form control using Angular's FormBuilder service. This allows us to define the initial value for each form field, which is derived from the value property in the configuration. If no default value is provided, we simply set an empty string as the default.

Additionally, we have the flexibility to apply custom validation rules to each field by utilizing the validators property in the configuration. This ensures that the form fields adhere to specific requirements and constraints, providing a reliable and interactive user experience.

The true beauty of this approach lies in its adaptability. By making changes to the configuration, such as adding new fields or altering their order, the form dynamically adjusts to reflect these modifications. As a result, we have a highly flexible and efficient way to create complex and interactive forms that can cater to various scenarios.

//...
export class App {
  myForm!: FormGroup;
  dynamicFormFields: DynamicFormFieldModel[] = [
    // ...
  ];

  constructor(private fb: FormBuilder) {
    this.myForm = this.fb.group({});
    this.setupFormFields();
  }

  setupFormFields() {
    this.dynamicFormFields.forEach((formItem: DynamicFormFieldModel) => {
      let formControl;
      formControl = this.fb.control(formItem.value || '', formItem.validators);
      this.myForm.addControl(formItem.id, formControl);
    });
  }}
Enter fullscreen mode Exit fullscreen mode

Displaying the Dynamic Form in the App Component

Finally, in app.component.html, we render the dynamic form fields by iterating through the dynamicFormFields array. Each form field is displayed using the dynamic-form-field component.

<ng-container [formGroup]="myForm">
  <!-- Generator -->
  <ng-container *ngFor="let formItem of dynamicFormFields">
    <dynamic-form-field [formItem]="formItem"></dynamic-form-field>
  </ng-container>
</ng-container>
<section>
  <button mat-raised-button color="primary" [disabled]="!myForm.valid">
    Click me
  </button>
</section>
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! In this article, we've successfully created dynamic and interactive forms in Angular by utilizing a configuration-driven approach. By leveraging the DynamicFormFieldModel, customizing form properties, and using ngSwitch, we've built a powerful form component that can handle various form fields with ease.

Stay tuned for the next article, where we'll dive deeper into the magic of dynamic forms. We'll explore how to break down each form field into separate components, further enhancing the reusability and maintainability of our dynamic forms. Until then, happy coding! 🚀

Top comments (0)