Stop re-implementing ControlValueAccessor
Tags: frontend, webdev, angular
Published: 2023-06-05 11:31:55


![Drake meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ho24jzlhve0uc59gziyb.jpg)

Almost every developer has come to the point where he wants to create his own form components, like a custom text input, a pretty file picker, or just a wrapper component of a library. 

To make sure that this custom component works on Template and Reactive forms you will need to implement the `ControlValueAccessor`, but some of the times this can be receptive and unnecessary if all you want is to pass the value without changing it.

In this article, I will showcase a way to avoid re-implementing the `ControlValueAccessor` but still be able to use the Forms API.

## ControlValueAccessor

> Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM. - Angular Docs

I will not dive into the ways this interface works, but here is a basic implementation example:

```typescript
@Component({
  standalone: true,
  imports: [FormsModule],
  selector: 'app-custom-input',
  template: `
    <input
      [ngModel]="value"
      (ngModelChange)="onChange($event)"
      [disabled]="isDisabled"
      (blur)="onTouched()"
    />
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: CustomInputComponent,
  }],
})
export class CustomInputComponent implements ControlValueAccessor {

  value: string;
  isDisabled: boolean;
  onChange: (value: string) => void;
  onTouched: () => void;

  writeValue(value: any) {
    this.value = value || '';
  }

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

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

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

Implementing this is not hard, but it can be lengthy and repetitive. Most of the time we don't even want to change the way the value is bound to the input, so using the angular provided binds would be enough.
Let's look at a way we can stop re-implementing the same logic once and once again.

## NgModel under the hood

Let's have a peek at how the `ngModel` directive syncs its value with the component.

The directive injects the `NG_VALUE_ACCESSOR` to be able to interact with the underlying component, this is the reason why we always need to provide it in our form components.
For the browser native inputs, angular includes some directives that provide `NG_VALUE_ACCESSOR`, one of them being the `DefaultValueAccessor`.

The `ngModel` creates a `FormControl` that it uses to keep the state of the control, like value, disabled, touched...

![Diagram of the components and how the ngModel directive interacts with them](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsuzn7gbbtdgprxesvyk.png)

Based on this we can see that our custom component looks like a bridge between the app and the `DefaultValueAccessor`, we can also see that there are 2 `FormControl` being created in this example, one on each `ngModel`.

Taking into account that our custom component is just a bridge and that the 1st `ngModel` already has a `FormControl`, we can grab this control and just pass it to the `input` without modifying it in any way.

## Accessing the directive control

The `ngModel`, `formControl`, and `formControlName` are the 3 directives that allow a component to interact with the forms API. From inside a component, we can have access to these directives by injecting the token `NgControl`.

![NgControl class diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtyjwzab6j6i1a5bvl7q.png)

So doing this we will have access to the directive that in turn has the `FormControl` that we can use.

Here is an example of how this would look like, but for now we still need a dummy value accessor for Angular to see the component as valid for form use.

```typescript
@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  selector: 'app-custom-input',
  template: `
    <input [formControl]="control" />
  `,
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: CustomInputComponent,
  }],
})
export class CustomInputComponent implements ControlValueAccessor, OnInit {

  control: FormControl;
  injector = inject(Injector);

  ngOnInit() {
    // ⬇ It can cause a circular dependency if injected in the constructor
    const ngControl = this.injector.get(NgControl, null, { self: true, optional: true });

    if (ngControl instanceof NgModel) {

      // ⬇ Grab the host control
      this.control = ngControl.control;

      // ⬇ Makes sure the ngModel is updated
      ngControl.control.valueChanges.subscribe((value) => {
        if (ngControl.model !== value || ngControl.viewModel !== value) {
          ngControl.viewToModelUpdate(value);
        }
      });

    } else {
      this.control = new FormControl();
    }
  }

  // ⬇ Dummy ValueAccessor methods
  writeValue() { }
  registerOnChange() { }
  registerOnTouched() { }
}
```

We need to also make sure the `viewToModelUpdate` is called when the value changes, so that the `ngModel` is kept updated and that the `ngModelChange` is triggered.

## FormControl and FormControlName

Let's have a look at how this can the extended to also work with the other directives. The `formControl` is the simplest one, all you need to add is this.

```typescript
if (ngControl instanceof FormControlDirective) {
  this.control = ngControl.control;
}
```

When using `formControlName`, to make sure we have the correct `fromControl` we need the `ControlContainer`, this is what keeps and manages all the controls in a form/group.

![ControlContainer class diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o96frs14045jhqog5bai.png)

So we can inject it and grab the control using the name of the control, like so.

```typescript
if (ngControl instanceof FormControlName) {
  const container = this.injector.get(ControlContainer).control as FormGroup;
  this.control = container.controls[ngControl.name] as FormControl;
  return;
}
```

## Reuse with Directive Composition 

With the 3 directives working, we can look at how to arrange this in a way that is simple to use. I think that the Directive Composition API is a good fit for this.

So if we put all the pieces together in a Directive, this is how it should look like.

```typescript
@Directive({
  standalone: true,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: HostControlDirective,
    },
  ],
})
export class HostControlDirective implements ControlValueAccessor {

  control: FormControl;

  private injector = inject(Injector);
  private subscription?: Subscription;

  ngOnInit() {
    const ngControl = this.injector.get(NgControl, null, { self: true, optional: true });

    if (ngControl instanceof NgModel) {
      this.control = ngControl.control;
      this.subscription = ngControl.control.valueChanges.subscribe((value) => {
        if (ngControl.model !== value || ngControl.viewModel !== value) {
          ngControl.viewToModelUpdate(value);
        }
      });

    } else if (ngControl instanceof FormControlDirective) {
      this.control = ngControl.control;
    
    } else if (ngControl instanceof FormControlName) {
      const container = this.injector.get(ControlContainer).control as FormGroup;
      this.control = container.controls[ngControl.name] as FormControl;

    } else {
      this.control = new FormControl();
    }
  }

  writeValue() { }
  registerOnChange() { }
  registerOnTouched() { }

  ngOnDestroy() {
    this.subscription?.unsubscribe();
  }
}
```

And with all the code being in the reusable directive, our custom components looks very clean.

```typescript
@Component({
  standalone: true,
  imports: [ReactiveFormsModule],
  selector: 'app-custom-input',
  template: `
    <input [formControl]="hcd.control" />
  `,
  hostDirectives: [HostControlDirective],
})
export class CustomInputComponent {
  hcd = inject(HostControlDirective);
}
```