3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Lesson 6

Creating Custom Controls

Build your own form inputs

EXTENDED

Creating Custom Controls

The last scenario we are going to cover is creating your own custom form inputs. We can use standard HTML inputs in our Angular forms:

<input formControlName="name" type="text" />

If we are using a component library, like Ionic for example, then we might also use custom inputs that look like this:

<ion-input formControlName="name" type="text"></ion-input>

Now, these clearly aren’t standard HTML form controls, so how is it that Angular is still able to treat these custom components as if they were normal form controls?

Introducing the ControlValueAccessor

This is one of those things that sounds intimidating, but when you break it down it makes more sense. The ControlValueAccessor is an interface we can implement, just like we have implemented interfaces like PipeTransform for creating pipes or AsyncValidator for creating an asynchronous validator.

The point of implementing ControlValueAccessor for a component is that it tells Angular how to treat this component as a normal form input that works with both reactive forms (what we have been using) and template driven forms (e.g. [(ngModel)]). Specifically, implementing this interface for a component will let Angular know:

  1. How to update the current value of the input (e.g. if we were to call setValue on the form control)
  2. When the value has been changed
  3. When the control has been interacted with

We can implement whatever kind of wacky form input we want - as long as we implement this interface that lets Angular know how it should treat it within a form. Simple time pickers? Boring! If we want we could implement a component that uses an SVG of a blazing sun and allow the user to drag this giant fireball across the time in order to set the time of day! That’s probably a terrible idea, but my point is that you could do it, and you could treat that ridiculous control like any other standard input that is compatible with things like formControlName and [(ngModel)].

Let’s take a closer look at what this interface looks like:

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

NOTE: The last function setDisabledState is optional

It is probably easier to walk through this interface with an actual example.

Creating a Component that Uses ControlValueAccessor

We are going to implement a simple component that has three buttons:

  • Sad
  • Neutral
  • Happy

The user will be able to click one of these and that should be the value that Angular forms uses. Basically, this is a slightly worse implementation of a standard radio input, but it makes for an easier to follow example.

First, let’s create the component without any consideration of how it will work within a form:

import { ChangeDetectionStrategy, Component, NgModule } from '@angular/core';

@Component({
  selector: 'app-happiness-level',
  template: `
    <div>
      <button (click)="mood = 'sad'" [class.active]="mood === 'sad'">
        Sad
      </button>
      <button (click)="mood = 'neutral'" [class.active]="mood === 'neutral'">
        Neutral
      </button>
      <button (click)="mood = 'happy'" [class.active]="mood === 'happy'">
        Happy
      </button>
    </div>
  `,
  styles: [
    `
      .active {
        font-weight: bold;
      }
    `,
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HappinessLevelComponent {
  mood = 'neutral';
}

@NgModule({
  declarations: [HappinessLevelComponent],
  exports: [HappinessLevelComponent],
})
export class HappinessLevelComponentModule {}

We have three buttons we can click and they will change the mood value. Whichever value is currently selected will be bold. Now let’s add in the ControlValueAccessor interface:

import { ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
  selector: 'app-happiness-level',
  // ...snip
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: HappinessLevelComponent,
      multi: true,
    },
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HappinessLevelComponent implements ControlValueAccessor {
  mood = 'neutral';
}
EXTENDED
Key

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).