DEV Community

Cover image for Part 3: Maximizing Angular Forms with Dynamic Custom Form Controls and Styling
Loukas Kotas
Loukas Kotas

Posted on

Part 3: Maximizing Angular Forms with Dynamic Custom Form Controls and Styling

Introduction

In the third part of our ongoing series on dynamic forms in Angular, we'll finalize our exploration, building on the foundation laid in Part 2: Custom Validation, Enhanced Interface, Error Handling, and Dynamic Data Binding
. In this article, we'll delve into how to isolate input and dropdown components into separate custom form controls, implement the ControlValueAccessor interface, and dynamically manage styles and classes for these custom form controls.

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! πŸš€

Isolating Input and Dropdown Components

In our pursuit of enhancing modularity and reusability in dynamic forms, we will establish two distinct components:

  • app-form-input: A component designed to encapsulate input form field.
  • app-form-dropdown: A component intended for encapsulating dropdown form field.

Our primary objective is to convert the input element into a custom form control by leveraging the ControlValueAccessor interface. Here's an example of how to implement the app-custom-input component:

<!-- custom-input.component.html -->
<input
  type="text"
  [value]="value"
  (keyup)="onValueChange($event)"
  (blur)="onTouched()"
/>
Enter fullscreen mode Exit fullscreen mode
// custom-input.component.ts
import { ChangeDetectionStrategy, Component } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

@Component({
  selector: "app-custom-input",
  templateUrl: "./custom-input.component.html",
  styleUrls: ["./custom-input.component.scss"],
  standalone: true,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: CustomInputComponent,
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomInputComponent implements ControlValueAccessor {
    value: any = ""; // This is the value of the custom input
    onChange: any = (value: any) => {};
    onTouched: any = () => {};

// This method is called when you want to update the custom input's value
// from an external source. It synchronizes the internal state with the
// provided 'value'.
    writeValue(value: any): void {
      this.value = value;
    }

    // This method is called when the value of the custom input changes
    registerOnChange(fn: any): void {
      this.onChange = fn;
    }

    // This method is called when the custom input is touched
    registerOnTouched(fn: any): void {
      this.onTouched = fn;
    }

    // These methods are required by the ControlValueAccessor interface
    markAsTouched(): void {
      this.onTouched();
    }

    // This method is called when the value of the custom input changes
    onValueChange(event: any) {
      this.value = event.target.value; // Update the value of the custom input
      this.onChange(this.value); // This method call will bind the value of the custom input to the form control
      this.markAsTouched(); // This method call will mark the custom input as touched
    }
}
Enter fullscreen mode Exit fullscreen mode

These custom form controls need to implement the writeValue, registerOnChange, and registerOnTouched methods as mandated by the ControlValueAccessor interface. These methods ensure seamless integration with the Angular Reactive Form, supporting value updates, validations, and state changes.

Moving Input and Dropdown Components

With our custom form control components in place, we can now migrate the input and dropdown components from the app-form to these new custom components:

<!-- dynamic-form-field.component.html -->
<ng-container [formGroup]="formControlValueAccessor">
  <app-custom-input formControlName="customInputValue" />
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Using Custom Form Controls in Dynamic Forms

Having encapsulated each component within its own form control component, they are now ready for use in the dynamic form. These custom form controls can be effortlessly incorporated into the dynamic-form-field template:

<!-- dynamic-form-field.component.html -->
<div class="form-item-container">
  <ng-container *ngSwitchCase="'text'">
    <app-custom-input
      type="text"
      class="form-control"
      [formItem]="formItem"
      formControlName="{{ formItem.id }}"
    />
    <mat-error>
      <app-error-message [formItem]="formItem" />
    </mat-error>
  </ng-container>

  <ng-container *ngSwitchCase="'select'">
    <app-custom-dropdown
      class="form-control"
      [formItem]="formItem"
      [formControl]="getFormControl(formItem)"
      formControlName="{{ formItem.id }}"
    />
    <mat-error>
      <app-error-message [formItem]="formItem" />
    </mat-error>
  </ng-container>
</div>
Enter fullscreen mode Exit fullscreen mode

Managing Styles and Classes for Dynamic Custom Form Controls

One of the challenges we encounter after integrating custom form fields is dynamically handling styles and classes for these components. Since the components are dynamic and defined in the configuration, their styling cannot be pre-defined. Instead, we can pass styles and classes through the configuration, ensuring that class names align between the configuration and CSS files.

For instance, to apply a custom style to the "email" field, you can define these styles:

// movies.config.ts
{
  id: 'email',
  label: 'Email',
  type: 'text',
  validators: [
    // Validation rules here
  ],
  classes: ['email-field-style'], // Pass dynamic classes here
}
Enter fullscreen mode Exit fullscreen mode

The 'email-field-style' will be applied to the email form field at runtime.

In the HTML template, you can apply dynamic styles like this:

<!-- custom-input.component.html -->
<input
  [ngClass]="formItem.styles" <!-- Pass dynamic styles here -->
  matInput
  type="text"
  class="form-control"
  [value]="value"
  (keyup)="onValueChange($event)"
  (blur)="onTouched()"
/>
Enter fullscreen mode Exit fullscreen mode

By passing all the dynamic styles through the configuration then the dynamic styles will be applied to the dynamic form control.

In the SCSS file, define the styling for the "email-field-style" class. This approach allows you to attach custom classes and styles to the dynamic form controls without causing unintended side effects.

By following these steps, you can isolate input and dropdown components, create custom form controls, and manage dynamic styles and classes effectively, enhancing the flexibility and reusability of your dynamic forms in Angular.

// global_styles.scss
.email-field-style.ng-touched.ng-invalid {
  #email {
    .mdc-text-field {
      border: 2px solid orange;
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Summary

In this third part of our Angular dynamic forms series, πŸš€ we focus on maximizing modularity, reusability, and dynamic styling. We demonstrate how to isolate input and dropdown components into separate custom form controls, implement the ControlValueAccessor interface for seamless integration, and manage 🎨 dynamic styles and classes. This approach enhances form flexibility and reusability, allowing you to create more dynamic and user-friendly Angular forms. πŸ› οΈ

Top comments (0)