Create Custom Form Control in Angular
Tags: angular, beginners, webdev, javascript
Published: 2023-09-06 14:20:49

Angular provides a powerful way to create custom form controls to enhance the functionality and reusability of your application. In this article, we'll walk through the process of creating a custom form control step by step, with a detailed explanation, background on `NG_VALUE_ACCESSOR`, and a practical example.

## Background on NG_VALUE_ACCESSOR

In Angular, `NG_VALUE_ACCESSOR` is a special injection token used to register and provide a way for Angular forms to communicate with custom form controls. It's a crucial part of creating custom form controls because it allows Angular to integrate them seamlessly into the reactive forms framework.

When you define a custom form control that implements the `ControlValueAccessor` interface and provide it using `NG_VALUE_ACCESSOR`, you're essentially telling Angular how to interact with your custom control. This includes syncing values, handling changes, and setting up two-way data binding.

## ControlValueAccessor Methods

The `ControlValueAccessor` interface defines several methods that you must implement in your custom form control class. These methods enable the interaction between your custom control and Angular forms:

```typescript
interface ControlValueAccessor<T> {
  writeValue(obj: T): void;
  registerOnChange(fn: (value: T) => void): void;
  registerOnTouched(fn: () => void): void;
  setDisabledState?(isDisabled: boolean): void;
}
```

1. `writeValue(value: any)`: This method is used to write a value from the model into the view. It's called when you programmatically set the value of your custom form control. Inside this method, you typically update the internal state of your control.

2. `registerOnChange(fn: any)`: This method registers a callback function that Angular calls whenever the value of your custom control changes. You should store this callback and call it when your control's value changes to notify Angular.

3. `registerOnTouched(fn: any)`: This method is similar to `registerOnChange`, but it's used to register a callback function that Angular calls when the custom control is "touched" or loses focus. It's typically used to mark the control as touched and trigger validation.

4. `setDisabledState(isDisabled: boolean)`: This optional method allows you to set the disabled state of your custom control. It's useful when you want to enable or disable the control programmatically.

Now we will see how we can create a Poll form control using the concept we have learnt.

## Detailed Steps to Create a Custom Poll Control

**Prerequisites**

Before we dive into creating custom form controls, make sure you have the following prerequisites installed:

1. Node.js and npm (Node Package Manager)
2. Angular CLI (Command Line Interface) - You can install it globally using `npm install -g @angular/cli`.

### Step 1: Set Up the Angular Project

If you haven't already, create a new Angular project using the Angular CLI:

```bash
ng new custom-poll-control-example
```

Navigate to the project directory:

```bash
cd custom-poll-control-example
```

### Step 2: Create a Custom Poll Control

Now, create a custom poll control component using the Angular CLI:

```bash
ng generate component poll-control
```

### Step 3: Implement the Custom Poll Control with Value Accessor Methods

In the `poll-control` component, open the `poll-control.component.ts` file, and define the custom form control with value accessor methods:

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

@Component({
  selector: 'app-poll-control',
  templateUrl: './poll-control.component.html',
  styleUrls: ['./poll-control.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PollControlComponent),
      multi: true,
    },
  ],
})
export class PollControlComponent implements ControlValueAccessor, OnInit {
  @Input() pollOptions: string[]; // Input property to receive poll options
  selectedOptions: string[] = [];
  onChange: any = () => {};
  onTouched: any = () => {};

  constructor() {}

  ngOnInit() {}

  writeValue(value: string[]) {
    this.selectedOptions = value;
  }

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

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

  setDisabledState(isDisabled: boolean) {
    // Implement if needed
  }

  updateSelectedOptions(option: string) {
    if (this.isSelected(option)) {
      this.selectedOptions = this.selectedOptions.filter((item) => item !== option);
    } else {
      this.selectedOptions.push(option);
    }
    this.onChange(this.selectedOptions);
    this.onTouched();
  }

  isSelected(option: string) {
    return this.selectedOptions.includes(option);
  }
}
```

In this code, we implement the `ControlValueAccessor` interface with methods like `writeValue`, `registerOnChange`, and `registerOnTouched`. We also create a method `updateSelectedOptions` to handle checkbox changes and update the form control value.

### Step 4: Create the Custom Poll Control Template

In the `poll-control` component directory, open the `poll-control.component.html` file, and create the HTML template for the poll control:

```html
<div>
  <div *ngFor="let option of pollOptions">
    <label>
      <input
        type="checkbox"
        [checked]="isSelected(option)"
        (change)="updateSelectedOptions(option)"
      />
      {{ option }}
    </label>
  </div>
</div>
```

### Step 5: Using the Custom Poll Control

In a parent component (e.g., `app.component.html`), use the custom poll control to create and display a poll:

```html
<form [formGroup]="pollForm">
  <app-poll-control [pollOptions]="options" formControlName="pollOptions"></app-poll-control>
  <button (click)="submitPoll()">Submit Poll</button>
</form>
```

### Step 6: Handling the Poll Data

In your parent component (e.g., `app.component.ts`), define a FormGroup for the poll form, handle the submitted data, and set up the form control:

```typescript
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent {
  options: string[] = ['BreakFast', 'Lunch', 'Dinner'];
  pollForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.pollForm = this.fb.group({
      pollOptions: [[]], // Initialize with an empty array
    });
  }

  submitPoll() {
    const selectedOptions = this.pollForm.value.pollOptions;
    console.log('Selected Poll Options:', selectedOptions);
  }
}
```

## Output

![UI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u5ovvj4v441q7p9bqkmz.png)

![Console Log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttf3n2bkd7qg5oj0zelp.png)

### Conclusion

In this example, we created a custom poll control in Angular using the `ControlValueAccessor` interface. Users can select multiple options, and the parent component can handle the selected options. This demonstrates how you can create more complex custom form controls in Angular to suit your application's requirements. You can further enhance this example by adding features like dynamic poll creation and result display.

Thank's for your time, see you soon :)