Home > Back-end >  Angular digits only directive still allows some characters to be written
Angular digits only directive still allows some characters to be written

Time:04-11

While I was researching how to best do the digits only directive on the internet, I found the following result close to my own solution:

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

@Directive({
  selector: '[appOnlyDigits]'
})

export class OnlyDigitsDirective {
  private navigationKeys = [
    'Backspace',
    'Delete',
    'Tab',
    'Escape',
    'Enter',
    'Home',
    'End',
    'ArrowLeft',
    'ArrowRight',
    'Clear',
    'Copy',
    'Paste'
  ];

  inputElement: HTMLElement;

  constructor(public el: ElementRef) {
    this.inputElement = el.nativeElement;
  }

  @HostListener('keydown', ['$event'])
  onKeyDown(e: KeyboardEvent) {
    if (
      this.navigationKeys.indexOf(e.key) > -1 || // Allow: navigation keys: backspace, delete, arrows etc.
      (e.key === 'a' && e.ctrlKey === true) ||   // Allow: Ctrl A
      (e.key === 'c' && e.ctrlKey === true) ||   // Allow: Ctrl C
      (e.key === 'v' && e.ctrlKey === true) ||   // Allow: Ctrl V
      (e.key === 'x' && e.ctrlKey === true) ||   // Allow: Ctrl X
      (e.key === 'a' && e.metaKey === true) ||   // Allow: Cmd A (Mac)
      (e.key === 'c' && e.metaKey === true) ||   // Allow: Cmd C (Mac)
      (e.key === 'v' && e.metaKey === true) ||   // Allow: Cmd V (Mac)
      (e.key === 'x' && e.metaKey === true)      // Allow: Cmd X (Mac)
    ) {
      // let it happen, don't do anything.
      return;
    }
    // Ensure that it is a number and stop the keypress.
    if (
      (e.shiftKey || (e.key < '0' || e.key > '9')) &&
      (e.key < 'numpad 0' || e.key > 'numpad 9')
    ) {
      e.preventDefault();
    }
  }

  @HostListener('paste', ['$event'])
  onPaste(event: ClipboardEvent) {
    event.preventDefault();
    const pastedInput: string = event.clipboardData!
      .getData('text/plain')
      .replace(/\D/g, ''); // get a digit-only string
    document.execCommand('insertText', false, pastedInput);
  }

  @HostListener('drop', ['$event'])
  onDrop(event: DragEvent) {
    event.preventDefault();
    const textData = event.dataTransfer!.getData('text').replace(/\D/g, '');
    this.inputElement.focus();
    document.execCommand('insertText', false, textData);
  }
}

But when I press Shift 3 twice in a row, it allows typing '^' character. And sometimes it also writes the ' ' character.

How can I fix this problem without changing the directive structure?

CodePudding user response:

Try this one, I'm using it in my ongoing project works perfectly.

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

@Directive({
  selector: '[appNumbersOnly]'
})
export class OnlyNumbersDirective {
  constructor(private el: ElementRef) { }

  @Input() allowMultiLine = false;
  @Input() allowNegative = false;
  @Input() allowDecimal = false;
  @Input() maxLength = 0;
  regex: RegExp;

  @HostListener('keypress', ['$event'])
  onKeyPress(event: KeyboardEvent) {
    this.validate(event, event.key === 'Enter' ? '\n' : event.key);
  }

  @HostListener('paste', ['$event'])
  onPaste(event: Event) {
    const pastedText = ( window as any).clipboardData && ( window as any).clipboardData.getData('Text') // If IE, use window
      || ( event as ClipboardEvent) && ( event as ClipboardEvent).clipboardData.getData('text/plain'); // Non-IE browsers
    this.validate(event, pastedText);
  }

  @HostListener('cut', ['$event'])
  onCut(event: Event) {
    this.validate(event, '');
  }

  validate(event: Event, text: string) {
    const txtInput = this.el.nativeElement;
    const newValue = (txtInput.value.substring(0, txtInput.selectionStart)
        text   txtInput.value.substring(txtInput.selectionEnd));
    if (!this.regex) {
      // tslint:disable-next-line: no-eval
      this.regex = ( eval('/^'
          (this.allowNegative ? '-?' : '')
          (this.allowDecimal ? '((\\d \\.?)|(\\.?))\\d*' : '\\d*')
          '$/g') as RegExp);
    }
    const lines = this.allowMultiLine ? newValue.split('\n') : [newValue];
    for (const line of lines) {
      const lineText = line.replace('\r', '');
      if (this.maxLength && lineText.length > this.maxLength || !lineText.match(this.regex)) {
        event.preventDefault();
        return;
      }
    }
  }

}

CodePudding user response:

Although I can't reproduce this, a solid failsafe is to just perform a replace on the value of the input. You just need to change the type of input element to HTMLInputElement first. Then add the replace wherever necessary, one, two, or all three of these places:

  inputElement: HTMLInputElement;

  constructor(public el: ElementRef) {
    this.inputElement = el.nativeElement;
  }

  @HostListener('keydown', ['$event'])
  onKeyDown(e: KeyboardEvent) {
    if (
      this.navigationKeys.indexOf(e.key) > -1 || // Allow: navigation keys: backspace, delete, arrows etc.
      (e.key === 'a' && e.ctrlKey === true) || // Allow: Ctrl A
      (e.key === 'c' && e.ctrlKey === true) || // Allow: Ctrl C
      (e.key === 'v' && e.ctrlKey === true) || // Allow: Ctrl V
      (e.key === 'x' && e.ctrlKey === true) || // Allow: Ctrl X
      (e.key === 'a' && e.metaKey === true) || // Allow: Cmd A (Mac)
      (e.key === 'c' && e.metaKey === true) || // Allow: Cmd C (Mac)
      (e.key === 'v' && e.metaKey === true) || // Allow: Cmd V (Mac)
      (e.key === 'x' && e.metaKey === true) // Allow: Cmd X (Mac)
    ) {
      // let it happen, don't do anything.
      this.inputElement.value = this.inputElement.value.replace(/\D/g, '');
      return;
    }
    // Ensure that it is a number and stop the keypress.
    if (
      (e.shiftKey || e.key < '0' || e.key > '9') &&
      (e.key < 'numpad 0' || e.key > 'numpad 9')
    ) {
      e.preventDefault();
    }
    this.inputElement.value = this.inputElement.value.replace(/\D/g, '');
  }

  @HostListener('keyup', ['$event'])
  onKeyUp(e: KeyboardEvent) {
    this.inputElement.value = this.inputElement.value.replace(/\D/g, '');
  }
  • Related