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()"
/>
// 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
}
}
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>
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>
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
}
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()"
/>
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;
}
}
}
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)