Search code examples
angulartypescriptangular-reactive-formsangular-directiveangular17

Angular - Validator not working when pasting the value to control with Phone Directive to format value


We are using Angular 17 along with Reactive forms in our project.
We have written a custom directive which formats the output to US phone number format 111-222-3333.
What we are seeing is that when someone tries to copy a number into the field - the field gets formatted, but the validator is still saying it is not valid.

HTML Code:

 <input matInput phoneFormatterDirective placeholder="Enter Phone" formControlName="phone"
                class="inputEntry">

Typescript Code:

phone: new FormControl(null, [Validators.pattern('^[0-9]{3}-[0-9]{3}-[0-9]{4}$')])

Custom Directive Code:

import {Directive, HostListener} from '@angular/core';

@Directive({
  selector: '[phoneFormatterDirective]'
})
export class PhoneFormatterDirective {

  @HostListener('input', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    event.preventDefault();
    const input = event.target as HTMLInputElement;
    console.log(input.value);
    let trimmed = input.value
      .replaceAll('-','')
      .replaceAll('(','')
    .replaceAll(')','')
    .replaceAll(/\s+/g, '');
    if (trimmed.length > 12) {
      trimmed = trimmed.substr(0, 12);
    }
    let numbers = [];
    numbers.push(trimmed.substr(0,3));
    if(trimmed.substr(3,2)!=="")
      numbers.push(trimmed.substr(3,3));
    if(trimmed.substr(6,3)!="")
      numbers.push(trimmed.substr(6,4));

    input.value = numbers.join('-');
    console.log(numbers.join("-"));
  }
}

Let's say I am trying to paste (555) 123-1234 - the value gets formatted to 555-123-1234, but the input says it is still invalid.
It would be valid if I deleted one character and then wrote it manually - which is a kind of strange behavior.

enter image description here


Solution

  • Currently, you are setting the formatted value to HtmlInputElement's value, but it is not reflected in the form control's state.

    I would suggest grabbing and setting the value from/to NgControl in order to update the form control's state.

    import { Directive, HostListener } from '@angular/core';
    import { NgControl } from '@angular/forms';
    
    @Directive({
      selector: '[phoneFormatterDirective]',
      standalone: true,
    })
    export class PhoneFormatterDirective {
      constructor(public control: NgControl) {}
    
      @HostListener('input', ['$event'])
      onKeyDown(event: KeyboardEvent) {
        event.preventDefault();
    
        const input = this.control.control!;
    
        console.log(input.value);
        let trimmed = input.value
          .replaceAll('-', '')
          .replaceAll('(', '')
          .replaceAll(')', '')
          .replaceAll(/\s+/g, '');
        if (trimmed.length > 12) {
          trimmed = trimmed.substr(0, 12);
        }
    
        let numbers = [];
        numbers.push(trimmed.substr(0, 3));
    
        if (trimmed.substr(3, 2) !== '') numbers.push(trimmed.substr(3, 3));
        if (trimmed.substr(6, 3) != '') numbers.push(trimmed.substr(6, 4));
    
        input!.setValue(numbers.join('-'), { emitEvent: false });
      }
    }
    

    Demo @ StackBlitz