DEV Community 👩‍💻👨‍💻

Cover image for How to Make a Component Compatible with Angular Forms?
Abimael Barea
Abimael Barea

Posted on • Updated on

How to Make a Component Compatible with Angular Forms?

The Angular framework provides 2 ways of creating forms:

  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.

interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}
Enter fullscreen mode Exit fullscreen mode

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


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

Component Implementation

Component Code:

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Template code:

<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>

Enter fullscreen mode Exit fullscreen mode

SCSS:

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

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

Enter fullscreen mode Exit fullscreen mode

Reactive Form usage

The component is compatible with the directives: formControlName and formControl.

<form [formGroup]="formGroup">
  <app-custom-component
    [formControlName]="controlsRef.Mood"
  ></app-custom-component>
</form>
Enter fullscreen mode Exit fullscreen mode

Template Driven Form usage

The component is also compatible with ngModel property:

<form>
  <app-custom-component
    [disabled]="disabled"
    [(ngModel)]="selectedMood"
    [ngModelOptions]="{ standalone: true }"
  ></app-custom-component>
</form>
Enter fullscreen mode Exit fullscreen mode

Full example

The detailed implementation is in one of my Github repos:

Top comments (0)

DEV

Thank you.

 
Thanks for visiting DEV, we’ve worked really hard to cultivate this great community and would love to have you join us. If you’d like to create an account, you can sign up here.