Angular is a TypeScript-based open-source web application framework led by the Angular Team at Google and a community of individuals and corporations. In this article, we’ll explore how to create a custom control in Angular that can be used as a reactive form control. Angular provides extensive extensibility features to enhance the behavior of the framework.
To create a custom control, we need to implement the ControlValueAccessor interface, which includes the following four methods:
interface ControlValueAccessor {
  writeValue(obj: any): void
  registerOnChange(fn: any): void
  registerOnTouched(fn: any): void
  setDisabledState(isDisabled: boolean)?: void
}
For this article, we’ll create a custom spinner control. Let’s start by creating a new Angular application using the following command:
ecuting the following command
ng new app_name
Next, create a component called SpinnerControl and paste the following code. The code is self-explanatory and creates the custom spinner control.
import { Component, forwardRef, Input } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
@Component({
  selector: "app-number",
  template: `
    <div class="container">
      <div class="left">
        <input
          class="number-input"
          [value]="value"
          type="number"
          [disabled]="disabled"
        />
      </div>
      <div class="right">
        <button (click)="onIncrement()">+</button>
        <button (click)="onDeincrement()">-</button>
      </div>
    </div>
  `,
  styles: [
    `
      .container {
        display: flex;
        padding: 0px;
      }
      .right {
        display: flex;
        flex-direction: column;
      }
      .number-input {
        height: 40px;
      }
      button {
        height: 20px;
        width: 20px;
        border: 0px;
        background-color: #aa33dd;
      }
    `
  ]
})
export class SpinnerControl implements ControlValueAccessor {
  @Input()
  public value: number;
  @Input()
  public disabled: boolean;
  onChanged: any = () => {};
  onTouched: any = () => {};
  writeValue(val): void {
    console.log(val);
    this.value = +val || 0;
  }
  registerOnChange(fn: any) {
    this.onChanged = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }
  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled;
  }
  numberChange($event) {
    this.onTouched();
    this.onChanged($event.target.value);
    this.setDisabledState(this.disabled);
  }
  onIncrement() {
    this.value = this.value + 1;
    this.onChanged(this.value);
  }
  onDeincrement() {
    this.value = this.value - 1;
    this.onChanged(this.value);
  }
}
| Method Name | Description | 
|---|---|
writeValue | 
Write a new value to the element | 
registerOnChange | 
Is called when the control values change in the UI | 
registerOnTouched | 
Is called by the forms API to update the form model on blur | 
In the above code, we’ve created the SpinnerControl component that implements the ControlValueAccessor interface. It includes the necessary methods to handle the control’s value, changes, and disabled state. The template defines the UI elements of the spinner control.
How to use?
Before using this custom reactive form control, you have to register this with angular providers array. See the below code for registration
providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SpinnerControl),
      multi: true
    }
  ]
NG_VALUE_ACCESSOR:NG_VALUE_ACCESSORis a predefined Angular token that represents the accessor for writing and reading the value of a control. It is used to connect the custom control to the Angular forms API.forwardRef:forwardRefis a function that is used to refer to a class that is not yet defined. In this case, it allows us to reference theSpinnerControlclass before it is fully defined.multi:multiis a boolean flag that specifies whether the provider is a multi-provider or a single-provider. In this case, we set it totrueto indicate that there can be multiple providers forNG_VALUE_ACCESSOR.
By including the NG_VALUE_ACCESSOR provider in the providers array, we register the SpinnerControl as a valid control that can be used within Angular’s reactive forms.
Final code looks like below.
import { Component, forwardRef, Input } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
@Component({
  selector: "app-number",
  template: `
    <div class="container">
      <div class="left">
        <input
          class="number-input"
          [value]="value"
          type="number"
          [disabled]="disabled"
        />
      </div>
      <div class="right">
        <button (click)="onIncrement()">+</button>
        <button (click)="onDeincrement()">-</button>
      </div>
    </div>
  `,
  styles: [
    `
      .container {
        display: flex;
        padding: 0px;
      }
      .right {
        display: flex;
        flex-direction: column;
      }
      .number-input {
        height: 40px;
      }
      button {
        height: 20px;
        width: 20px;
        border: 0px;
        background-color: #aa33dd;
      }
    `
  ],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SpinnerControl),
      multi: true
    }
  ]
})
export class SpinnerControl implements ControlValueAccessor {
  @Input()
  public value: number;
  @Input()
  public disabled: boolean;
  onChanged: any = () => {};
  onTouched: any = () => {};
  writeValue(val): void {
    console.log(val);
    this.value = +val || 0;
  }
  registerOnChange(fn: any) {
    this.onChanged = fn;
  }
  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }
  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled;
  }
  numberChange($event) {
    this.onTouched();
    this.onChanged($event.target.value);
    this.setDisabledState(this.disabled);
  }
  onIncrement() {
    this.value = this.value + 1;
    this.onChanged(this.value);
  }
  onDeincrement() {
    this.value = this.value - 1;
    this.onChanged(this.value);
  }
}
To use the custom control, you need to register it with the Angular providers array in the component where you want to use it. Here’s an example of how to register the SpinnerControl:
import { Component } from "@angular/core";
@Component({
  selector: "app-my-component",
  template: `
    <form [formGroup]="myForm">
      <app-spinner formControlName="spinnerControl"></app-spinner>
    </form>
  `
})
export class MyComponent {
  // ...
}
In the above code, we’re using the app-spinner component as a form control by binding it to the spinnerControl form control name.
That’s it! You’ve successfully created a custom spinner control in Angular that can be used as a reactive form control. The control implements the necessary ControlValueAccessor methods to integrate with Angular’s forms API.
Further reading
What is  forwardRef
From  Angular’s API docs on  forwardRef:
forwardRefis used when the token which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when the token which we use when creating a query is not yet defined.