'What event to listen to inside a directive for a user overwriting existing input

I have an angular form that has an input with a custom directive. This directive limits the user to only enter a single '+' by having a keydown hostListener that checks the control's current value to see if there is already a '+', returning a boolean to allow the key input accordingly. This works fine if I'm typing normally, but not when I highlight the existing input that has a '+' and type '+'. The hostlistener on keydown still has the original value of the control, which means that I am binding to the wrong event when performing this restriction. I'm already performing some additional validation in the 'change' event, but that only gets fired once the user submits their input.

Which event should I be listening to to properly capture the control's current text before it is submitted?

const defaultOptions = {
  showPositiveSign: true,
};

@Directive({
  selector: '[myNumeric]',
})

export class NumericDirective implements OnChanges {
  @Input('myNumeric') public options = defaultOptions;

  public constructor(@Optional() @Self() private ngControl: NgControl) {}

  @HostListener('keydown', ['$event']) public onKeyDown(
    input: KeyboardEvent
  ): boolean {
    const inputValue = (input.target as any).value;
    const alreadySigned = inputValue.includes('+') || inputValue.includes('-'); // value is the control's value before replacement

    if (input.key === '+') {
      return this.options.showPositiveSign && !alreadySigned && this.isEmpty(inputValue);
    }
  }
  @HostListener('change') public onChange() {
    if (this.ngControl) {
      let value = this.ngControl.value; // value is the control's value after replacement
      // additional validation
    }
  }

  private isEmpty(value: any): boolean {
    return value === null || value === undefined || value === '';
  }
}

Reproduce:

  1. bind to an input
  2. type in +1.0
  3. highlight everything and type +

Alternatively, I could perform all validation in the change event and remove all '+' signs if they aren't the first character



Solution 1:[1]

I needed to use both KeyDown and KeyUp to properly handle this.

KeyDown could restrict each character that was typed before it was accepted into the input's value, so I adjusted my validations in keydown to the bare minimum to only check if '+' was accepted at all.

@HostListener('keydown', ['$event']) public onKeyDown(
    input: KeyboardEvent
  ): boolean {
    if (input.key === '+') {
      return this.options.showPositiveSign;
    }
  }

KeyUp reads symbol by symbol as they are accepted into the input's value. Here is where I would apply additional validations and modify the control's value accordingly

@HostListener('keyup', ['$event']) public onKeyUp(
    input: KeyboardEvent
  ): boolean {
    const inputValue = (input.target as HTMLInputElement).value;
    const cleanedInput = this.removeExtraSymbols(inputValue);
    if (inputValue.toString() !== cleanedInput.toString()) {
      this.ngControl.control.setValue(cleanedInput);
    }
  }

private removeExtraSymbols(value): any {
  // apply cleaning logic => if more than 1 '+', trim the extras
}

The user will see the second + typed into the field, but it will be removed immediately after. This is better than waiting for the change event, where they would need to wait until the input was accepted before seeing the extra symbols disappear

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 TabsNotSpaces