DEV Community

Sonu Kapoor for This is Angular

Posted on

Mastering Angular: Dynamic Disabled States with Reactive Forms and ControlValueAccessor

In Angular, Reactive Forms provide a powerful and flexible way to manage form inputs, validations, and user interactions. However, when dealing with custom form controls, you may encounter scenarios where you need to dynamically set the disabled property. This is where ControlValueAccessor comes into play.

What is ControlValueAccessor?

ControlValueAccessor is an interface in Angular that allows custom form controls to integrate seamlessly with Angular's forms API. It acts as a bridge between Angular forms and native DOM elements, ensuring that your custom control behaves like a standard form control.

Implementing ControlValueAccessor

To create a custom form control that supports the disabled property, you need to implement ControlValueAccessor. Here’s a basic example:

import { Component, forwardRef, Input } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

@Component({
  selector: "app-custom-input",
  template: `<input [disabled]="isDisabled" (input)="onInput($event)" />`,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true,
    },
  ],
})
export class CustomInputComponent implements ControlValueAccessor {
  @Input() isDisabled = false;

  private onChange: (value: any) => void;
  private onTouched: () => void;

  writeValue(value: any): void {
    // Implement the write value logic here
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }

  onInput(event: Event): void {
    const input = event.target as HTMLInputElement;
    this.onChange(input.value);
  }
}
Enter fullscreen mode Exit fullscreen mode

Setting the Disabled Property Dynamically

The setDisabledState method is critical here. Angular calls this method when the control needs to be disabled or enabled. By implementing this method, your custom form control will respect the disabled status set by Angular forms.

Warning: Directly Setting the Disabled Attribute

If you directly set the disabled property on an input element within a form control, Angular will throw a warning:

It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true when you define the form control, the disabled attribute will actually be set in the DOM for you. We recommend using this approach to avoid 'changed after checked' errors.

Properly Disabling Custom Controls

  1. Using setDisabledState in ControlValueAccessor:

This method ensures that your custom form control integrates properly with Angular's forms API, allowing Angular to manage the disabled state.

  1. Setting disabled via Form Control:

You can set the disabled state when defining the form control:

new FormControl({ value: "", disabled: true });
Enter fullscreen mode Exit fullscreen mode

Or programmatically disable the control using control.disable().

Using the Custom Control in a Reactive Form

You can now use your custom control within a reactive form, just like any other Angular form control:

import { Component } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";

@Component({
  selector: "app-root",
  template: `
    <form [formGroup]="form">
      <app-custom-input formControlName="customControl"></app-custom-input>
      <button type="button" (click)="toggleDisabled()">Toggle Disabled</button>
    </form>
  `,
})
export class AppComponent {
  form = new FormGroup({
    customControl: new FormControl(""),
  });

  toggleDisabled() {
    const control = this.form.get("customControl");
    control.disabled ? control.enable() : control.disable();
  }
}
Enter fullscreen mode Exit fullscreen mode

Disabling the Custom Control via Form Controls

Angular's Reactive Forms allow you to set the disabled state directly within the form control configuration:

@Component({
  selector: 'app-root',
  template: `
    <form [formGroup]="form">
      <app-custom-input formControlName="customControl"></app-custom-input>
    </form>
  `,
})
export class AppComponent {
  form = new FormGroup({
    customControl: new FormControl({ value: '', disabled: true }),
  });
}
Enter fullscreen mode Exit fullscreen mode

In this example, the customControl is initialized as disabled by default. You can toggle the disabled state programmatically as needed.

Conclusion

Using ControlValueAccessor to manage the disabled property in Angular custom form controls ensures consistent behavior across your application. Avoid directly setting the disabled attribute on the DOM element to prevent Angular warnings and potential errors. By following these best practices, you can create reusable, flexible, and accessible form components that integrate seamlessly with Angular’s Reactive Forms, making your application more robust and user-friendly.

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Sonu Kapoor,
Top, very nice and helpful !
Thanks for sharing.