How to use ControlValueAccessor to enhance date input with automatic conversion and validation
Tags: angular, typescript
Published: 2021-06-03 09:26:56

---
title: How to use ControlValueAccessor to enhance date input with automatic conversion and validation
published: true
description: In this article, we will learn how we can extend native date input through a directive so that it supports conversion of value and validation on value
tags: angular,typescript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5pi0pktjce6s2sqemqd.jpeg
canonical_url: https://indepth.dev/posts/1467/how-to-use-controlvalueaccessor-to-enhance-date-input-with-automatic-conversion-and-validation
---
Overall idea behind this article to explain and demonstrate the usage of `ControlValueAccessor` and `Validator` interfaces . The former is used to bind together a FormControl from Forms package and native DOM elements. The latter is used to implement validation logic. They can exist independently of each other, but in this article we’ll implement both using a single directive. Our directive will add the following functionality to the application:

1. Conversion between input value and control value
2. Validation for invalid date

If you’re using `ControlValueAccessor` for the first time, I would recommend going through this article first: [Never again be confused when implementing ControlValueAccessor in Angular forms](https://indepth.dev/posts/1055/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms). 

## Conversion of values

We will first create a directive and handle conversion between UI and control value.

```typescript
// src/app/directives/date-input.directive.ts

import { Directive } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Directive({
  selector: 'input[type=date]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: DateInputDirective,
      multi: true
    }
  ]
})
export class DateInputDirective implements ControlValueAccessor {
  constructor() {}
  writeValue(obj: any): void {}
  registerOnChange(fn: any): void {}
  registerOnTouched(fn: any): void {}
}
```

We mainly did 3 things for directive:

1. Set the selector to `input[type=date]` - This will add the conversion mechanism and validations to all date-inputs without any extra effort.
2. Defined `DateInputDirective` class as Value Accessor through the `NG_VALUE_ACCESSOR` token. Our directive will be used by Angular to set-up synchronisation with `FormControl`.
3. `implement`ed the `ControlValueAccessor` interface

For this example, we are only concerned with two methods on the interface:

1. `writeValue` - this method is used to write a value to the native DOM element. Simply put, this can be utilised to convert `FormControl` value to UI value.
2. `registerOnChange` - This method will help us to store a function, which will be called by value-changes on the UI. In simpler terms, with this we can convert UI value to `FormControl` value.

This illustration from the linked article above demonstrates this mechanism:

![This illustration from the linked article above demonstrates this mechanism](https://cdn.hashnode.com/res/hashnode/image/upload/v1622711830989/ScvpcAtnV.png)

## Writing value to the native DOM element with `writeValue`

We want to show the correct and formatted date on the UI when it gets updated through `FormControl`.

For example, let’s assume that we are getting [ISO string of date](https://www.w3.org/TR/NOTE-datetime) from our API, which looks something like this: `1994-11-05T08:15:30-05:00`, and when we set the value for `FormControl` bound to the date input, we want the `input[type=date]` to display date in correct format.

The code below demonstrates how we convert the ISO string into `YYYY-MM-DD` before we set the resulting value for the native [HTML date input](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date):

```typescript
// src/app/directives/date-input.directive.ts

import { formatDate } from '@angular/common';

// ...
export class DateInputDirective implements ControlValueAccessor {
  writeValue(dateISOString: string): void {
      const UIValue = formatDate(dateISOString, 'YYYY-MM-dd', 'en-IN');

      this._renderer.setAttribute(
        this._elementRef.nativeElement,
        'value',
        UIValue
      );
  }
}
```

Here’s what’s going on above:

1. We are creating a string called `UIValue`, which will hold the date in `YYYY-MM-DD` format. As you can see, we have used the [`formatDate`](https://angular.io/api/common/formatDate) function from `@angular/common` to get the formatted date.
2. And then, we are setting `input`’s `value` attribute using `Renderer2`

Let’s quickly try out the above changes.

```typescript
// src/app/app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  fg = new FormGroup({
    date: new FormControl(new Date().toISOString()),
  });

  get date() {
    return this.fg.get('date');
  }
}
```

Note 2 things in above code:

1. We set the current date’s ISO string in `date` `FormControl`, ideally you would get it from API.
2. We created a `date` getter to get the `FormControl`. In a reactive form, you can always access any form control through the `get` method on its parent group, but sometimes it's useful to define getters as shorthand for the template.

```html
<!-- src/app/app.component.html -->

<form [formGroup]="fg">
  <input
        type="date"
        id="birthDate"
        formControlName="date"
      />
  <div>
    <code>
        <b>Control Value: </b>{{ date.value }}
    </code>
  </div>
</form>
```

If you look at the output now, it is setting the correct date in input:

![Output after setting writeValue](https://cdn.hashnode.com/res/hashnode/image/upload/v1622711924429/MLYhqFPtG.png)

## Getting value from the native DOM element with `registerOnChange`

The DOM element holds the value as formatted date. When the user updates the value, we'll need to convert it to a valid ISO string.

Let’s add a `HostListener` first:

```typescript
// src/app/directives/date-input.directive.ts

// …

export class DateInputDirective implements ControlValueAccessor {

  // …

  @HostListener('input', ['$event.target.valueAsNumber'])
  onInput = (_: any) => {};
}
```

We are using `valueAsNumber` to read the value. `valueAsNumber` returns the timestamp in milliseconds, the reason we are using it is because it will help us directly get date using `new Date(valueAsNumber)`. Also notice the `onInput` function above, it’s just a skeleton for now.

It’s time to implement the conversion logic in `registerOnChange`:

```typescript
// src/app/directives/date-input.directive.ts

// …

export class DateInputDirective implements ControlValueAccessor {

  // …

  registerOnChange(fn: (_: any) => void): void {
      this.onInput = (value: number) => {
        fn(this.getDate(value).toISOString());
      };
  }
}
```

`registerOnChange` is called just once by Angular, passing us the callback named `fn` in the code above. We can use this callback to update the `FormControl` value as a reaction to DOM element update. And we are calling it on the `input` event of date-input through `onInput` function.

We also need to create a couple of helper functions, you can change them as per your need:

```typescript
  getDate(value: number) {
      if (value) {
        const dateObj = new Date(value);
        return this.isValidDate(dateObj) ? dateObj : { toISOString: () => null };
      }
      return { toISOString: () => null };
    }

  isValidDate(d: Date | number | null) {
      return d instanceof Date && !isNaN(d as unknown as number);
  }
```


Let’s look at the output now:

![Output after setting registerOnChange](https://cdn.hashnode.com/res/hashnode/image/upload/v1622712049749/CTIkd2cUv.gif)

As you can see, it’s updating the control's value with a valid ISO string.

## Validation

Now we will add the validation part so that date-input supports validation out-of-the box.

We will first add `NG_VALIDATORS` in providers:

```typescript
// src/app/directives/date-input.directive.ts

// …

@Directive({
  selector: 'input[type=date]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: DateInputDirective,
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: DateInputDirective,
      multi: true,
    },
  ],
})
```

Next, we will implement the `Validator` interface and add `validate` method:


```typescript
export class DateInputDirective implements ControlValueAccessor, Validator {

  // ...

  validate(control: AbstractControl): ValidationErrors | null {
    const date = new Date(control.value);
    return control.value && this.isValidDate(date) ? null : { date: true };
  }
}
```

Angular will call the `validate` method whenever the value of the control changes.

Let’s modify the template:

```html
<form [formGroup]="fg">
  <input
        type="date"
        id="birthDate"
        formControlName="date"
      />
  <div class="invalid-feedback" *ngIf="(date?.touched || date?.dirty) && date?.invalid">
        Invalid Date
  </div>
</form>
```

As you can see, we added a `div` to show a validation message. It uses the `date` getter defined in the component class.

Let’s understand the `*ngIf` expression:


```typescript
*ngIf="(date?.touched || date?.dirty) && date?.invalid"
```

1. We don’t want to show validation message if user has not interacted with the input, so we have added `date?.touched || date?.dirty`
2. We also don’t want to show if `date` is valid, so we added `date?.invalid`

You can read more about [Validating form input on Angular docs](https://angular.io/guide/form-validation#validating-form-input).

Let’s look at the output now:

![Output after implementing Validator](https://cdn.hashnode.com/res/hashnode/image/upload/v1622712111240/8bzSQUJnv.gif)

# Conclusion

We learned below:

✅  How to use `ControlValueAccessor` to

*   Convert UI value to valid ISO string and attach it to form-control’s value
*   Convert form-control’s ISO string value to `YYYY-MM-DD` format and update the same on UI

✅  How to use `Validator` to validate user input for date

You can find the code on [Stackblitz](https://stackblitz.com/edit/angular-ivy-khuzec?file=src/app/date-input.directive.ts) and [GitHub](https://github.com/shhdharmen/angular-date-input-directive).

Thanks for reading!