How to Make a Component Compatible with Angular Forms?
Tags: angular, webdev, components, tutorial
Published: 2021-12-27 18:27:51

The Angular framework provides [2 ways of creating forms](https://angular.io/guide/forms-overview#choosing-an-approach):

1. Reactive Forms
2. Template Driven

The content of this article is **valid to both of them**. 

-----

## Control Value Accessor (Interface)

> [Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM.](https://angular.io/api/forms/ControlValueAccessor)

```typescript
interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}
```
So, this is an interface provided by Angular that will allow us to make our components compatibles with Angular Forms.

## NG_VALUE_ACCESSOR (InjectionToken)

This element is essential as part of implementing a form-compatible component. Its usage is mainly to register the component. [More info](https://angular.io/api/forms/NG_VALUE_ACCESSOR)

-----

## Component

For the aim of this example, let's imagine that we want to build a component that allows you to select your mood, just like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7w20yzjspicu4vuo2q0b.png)

## Component Implementation

Component Code:

```typescript
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export enum Mood {
  Red = 'red',
  Green = 'green',
}

@Component({
  selector: 'app-custom-component',
  templateUrl: './custom-component.component.html',
  styleUrls: ['./custom-component.component.scss'],
  providers: [
    // This part is very important to register the class as a ControlValueAccessor one
    {
      provide: NG_VALUE_ACCESSOR,
      // This reference the class that implements Control Value Accessor
      useExisting: forwardRef(() => CustomComponentComponent),
      multi: true,
    },
  ],
})
export class CustomComponentComponent implements ControlValueAccessor {
  /* Reference to the Enum to be used in the template */
  readonly moodRef = Mood;
  disable: boolean = false;
  selected: Mood = Mood.Green;

  updateState(selectedItem: Mood): void {
    this.selected = selectedItem; // Updating internal state
    this.onChange(this.selected); // 'publish' the new state
  }

  /***********************************************************************
   * Control Value Accessor Implementation
   ***********************************************************************/

  private onChange: any;
  private onTouch: any;

  // Invoked by angular - update internal state
  writeValue(obj: any): void {
    this.selected = obj;
  }

  // Invoked by angular - callback function for changes
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  // Invoked by angular - callback function for touch events
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Invoked by angular - update disabled state
  setDisabledState?(isDisabled: boolean): void {
    this.disable = isDisabled;
  }
}
```

Template code: 

```html
<p>How do you feel?</p>

<ng-container *ngIf="!disable; else disabledTemplate">
  <button
    [ngClass]="{
      custom__button__red: true,
      'custom__button--selected': selected === moodRef.Red
    }"
    (click)="updateState(moodRef.Red)"
  >
    Red
  </button>
  <button
    [ngClass]="{
      custom__button__green: true,
      'custom__button--selected': selected === moodRef.Green
    }"
    (click)="updateState(moodRef.Green)"
  >
    Green
  </button>
</ng-container>

<ng-template #disabledTemplate>
  <p>I'm disabled</p>
</ng-template>

```

SCSS:

```CSS
.custom__button {
  &__red {
    background-color: red;
  }
  &__green {
    background-color: green;
  }

  &--selected {
    margin: 1em;
    border: solid 5px black;
  }
}

```
-----

## Reactive Form usage

The component is compatible with the directives: [formControlName](https://angular.io/api/forms/FormControlName) and [formControl](https://angular.io/api/forms/FormControl).

```html
<form [formGroup]="formGroup">
  <app-custom-component
    [formControlName]="controlsRef.Mood"
  ></app-custom-component>
</form>
```

## Template Driven Form usage

The component is also compatible with [ngModel](https://angular.io/api/forms/NgModel) property:

```html
<form>
  <app-custom-component
    [disabled]="disabled"
    [(ngModel)]="selectedMood"
    [ngModelOptions]="{ standalone: true }"
  ></app-custom-component>
</form>
```

-----

## Full example

The detailed implementation is in one of my Github repos:

- [Component](https://github.com/abimaelbarea/angularTutorials/tree/master/src/app/shared/custom-component)
- [Reactive Form usage](https://github.com/abimaelbarea/angularTutorials/tree/master/src/app/reactive-forms)
- [Template Driven Form usage](https://github.com/abimaelbarea/angularTutorials/tree/master/src/app/template-forms)