121

In Angular 2, how can I mask an input field (textbox) such that it accepts only numbers and not alphabetical characters?

I have the following HTML input:

<input 
  type="text" 
  *ngSwitchDefault 
  class="form-control" 
  (change)="onInputChange()" 
  [(ngModel)]="config.Value" 
  (focus)="handleFocus($event)" 
  (blur)="handleBlur($event)"
/>

The above input is a generic text input which may either be used as a simple text field or as a numeric field, for example, to show the year.

Using Angular 2, how can I use the same input control and apply some sort of filter/mask on this field, such that it accepts only numbers?

What are the different ways I can achieve this?

Note: I need to achieve this using only textbox and not using input number type.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
Aniruddha Pondhe
  • 1,830
  • 3
  • 17
  • 30

40 Answers40

134

You can use angular2 directives. Plunkr

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

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

  constructor(private el: ElementRef) { }

  @Input() OnlyNumber: boolean;

  @HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if (this.OnlyNumber) {
      if ([46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 ||
        // Allow: Ctrl+A
        (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+C
        (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+V
        (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+X
        (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) ||
        // Allow: home, end, left, right
        (e.keyCode >= 35 && e.keyCode <= 39)) {
          // let it happen, don't do anything
          return;
        }
        // Ensure that it is a number and stop the keypress
        if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
            e.preventDefault();
        }
      }
  }
}

and you need to write the directive name in your input as an attribute

<input OnlyNumber="true" />

don't forget to write your directive in declarations array of your module.

By using regex you would still need functional keys

export class OnlyNumber {

  regexStr = '^[0-9]*$';
  constructor(private el: ElementRef) { }

  @Input() OnlyNumber: boolean;

  @HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if (this.OnlyNumber) {
        if ([46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 ||
        // Allow: Ctrl+A
        (e.keyCode == 65 && e.ctrlKey === true) ||
        // Allow: Ctrl+C
        (e.keyCode == 67 && e.ctrlKey === true) ||
        // Allow: Ctrl+V
        (e.keyCode == 86 && e.ctrlKey === true) ||
        // Allow: Ctrl+X
        (e.keyCode == 88 && e.ctrlKey === true) ||
        // Allow: home, end, left, right
        (e.keyCode >= 35 && e.keyCode <= 39)) {
          // let it happen, don't do anything
          return;
        }
      let ch = String.fromCharCode(e.keyCode);
      let regEx =  new RegExp(this.regexStr);    
      if(regEx.test(ch))
        return;
      else
         e.preventDefault();
      }
  }
}
Belal mazlom
  • 1,790
  • 20
  • 25
omeralper
  • 9,804
  • 2
  • 19
  • 27
93

If you don't want a directive

https://stackblitz.com/edit/numeric-only

in component.html

<input (keypress)="numberOnly($event)" type="text">

in component.ts

export class AppComponent {

  numberOnly(event): boolean {
    const charCode = (event.which) ? event.which : event.keyCode;
    if (charCode > 31 && (charCode < 48 || charCode > 57)) {
      return false;
    }
    return true;

  }
}
rashidnk
  • 4,096
  • 2
  • 22
  • 32
  • 49
    The problem with this approach is that key events do not capture a user pasting or a browser auto filling the input field. So this is a poor solution. – Darryn Hosking Aug 21 '18 at 09:38
41

I know this is an old question, but since this is a common funcionality, I want to share the modifications I've made:

  • Custom decimal separator (point or comma)
  • Support for integers only or integer and decimals
  • Support for positive numbers only or positives and negatives
  • Validate minus sign(-) is in the beginning
  • Support to mouse pasting (with some limitation though https://caniuse.com/#feat=clipboard)
  • Support for Mac command key
  • Replace strings like ".33" and "33." for the correct versions: 0.33 and 33.0

    import { Directive, ElementRef, HostListener, Input } from '@angular/core';
    
    @Directive({ selector: '[NumbersOnly]' })
    export class NumbersOnly { 
    
        @Input() allowDecimals: boolean = true;
        @Input() allowSign: boolean = false;
        @Input() decimalSeparator: string = '.';
    
        previousValue: string = '';
    
        // --------------------------------------
        //  Regular expressions
        integerUnsigned: string = '^[0-9]*$';
        integerSigned: string = '^-?[0-9]+$';
        decimalUnsigned: string = '^[0-9]+(.[0-9]+)?$';
        decimalSigned: string = '^-?[0-9]+(.[0-9]+)?$';
    
        /**
         * Class constructor
         * @param hostElement
         */
        constructor(private hostElement: ElementRef) { }
    
        /**
         * Event handler for host's change event
         * @param e
         */
        @HostListener('change', ['$event']) onChange(e) {
    
                this.validateValue(this.hostElement.nativeElement.value);
    }
    
    /**
     * Event handler for host's paste event
     * @param e
     */
    @HostListener('paste', ['$event']) onPaste(e) {
    
        // get and validate data from clipboard
        let value = e.clipboardData.getData('text/plain');
        this.validateValue(value);
        e.preventDefault();
    }
    
    /**
     * Event handler for host's keydown event
     * @param event
     */
    @HostListener('keydown', ['$event']) onKeyDown(e: KeyboardEvent) {
    
        let cursorPosition: number = e.target['selectionStart'];
        let originalValue: string = e.target['value'];
        let key: string = this.getName(e);
        let controlOrCommand = (e.ctrlKey === true || e.metaKey === true);
        let signExists = originalValue.includes('-');
        let separatorExists = originalValue.includes(this.decimalSeparator);
    
        // allowed keys apart from numeric characters
        let allowedKeys = [
            'Backspace', 'ArrowLeft', 'ArrowRight', 'Escape', 'Tab'
        ];
    
        // when decimals are allowed, add
        // decimal separator to allowed codes when
        // its position is not close to the the sign (-. and .-)
        let separatorIsCloseToSign = (signExists && cursorPosition <= 1);
        if (this.allowDecimals && !separatorIsCloseToSign && !separatorExists) {
    
            if (this.decimalSeparator == '.')
                allowedKeys.push('.');
            else
                allowedKeys.push(',');
        }
    
        // when minus sign is allowed, add its
        // key to allowed key only when the
        // cursor is in the first position, and
        // first character is different from
        // decimal separator
        let firstCharacterIsSeparator = (originalValue.charAt(0) != this.decimalSeparator);
        if (this.allowSign && !signExists &&
            firstCharacterIsSeparator && cursorPosition == 0) {
    
            allowedKeys.push('-');
        }
    
        // allow some non-numeric characters
        if (allowedKeys.indexOf(key) != -1 ||
            // Allow: Ctrl+A and Command+A
            (key == 'a' && controlOrCommand) ||
            // Allow: Ctrl+C and Command+C
            (key == 'c' && controlOrCommand) ||
            // Allow: Ctrl+V and Command+V
            (key == 'v' && controlOrCommand) ||
            // Allow: Ctrl+X and Command+X
            (key == 'x' && controlOrCommand)) {
            // let it happen, don't do anything
            return;
        }
    
        // save value before keydown event
        this.previousValue = originalValue;
    
        // allow number characters only
        let isNumber = (new RegExp(this.integerUnsigned)).test(key);
        if (isNumber) return; else e.preventDefault();
    }
    
    /**
     * Test whether value is a valid number or not
     * @param value
     */
    validateValue(value: string): void {
    
        // choose the appropiate regular expression
        let regex: string;
        if (!this.allowDecimals && !this.allowSign) regex = this.integerUnsigned;
        if (!this.allowDecimals && this.allowSign) regex = this.integerSigned;
        if (this.allowDecimals && !this.allowSign) regex = this.decimalUnsigned;
        if (this.allowDecimals &&  this.allowSign) regex = this.decimalSigned;
    
        // when a numbers begins with a decimal separator,
        // fix it adding a zero in the beginning
        let firstCharacter = value.charAt(0);
        if (firstCharacter == this.decimalSeparator)
            value = 0 + value;
    
        // when a numbers ends with a decimal separator,
        // fix it adding a zero in the end
        let lastCharacter = value.charAt(value.length-1);
        if (lastCharacter == this.decimalSeparator)
            value = value + 0;
    
        // test number with regular expression, when
        // number is invalid, replace it with a zero
        let valid: boolean = (new RegExp(regex)).test(value);
        this.hostElement.nativeElement['value'] = valid ? value : 0;
    }
    
    /**
     * Get key's name
     * @param e
     */
    getName(e): string {
    
        if (e.key) {
    
            return e.key;
    
        } else {
    
            // for old browsers
            if (e.keyCode && String.fromCharCode) {
    
                switch (e.keyCode) {
                    case   8: return 'Backspace';
                    case   9: return 'Tab';
                    case  27: return 'Escape';
                    case  37: return 'ArrowLeft';
                    case  39: return 'ArrowRight';
                    case 188: return ',';
                    case 190: return '.';
                    case 109: return '-'; // minus in numbpad
                    case 173: return '-'; // minus in alphabet keyboard in firefox
                    case 189: return '-'; // minus in alphabet keyboard in chrome
                    default: return String.fromCharCode(e.keyCode);
                }
            }
        }
    }
    

Usage:

 <input NumbersOnly
        [allowDecimals]="true"
        [allowSign]="true"
        type="text">
Elvis Fernandez
  • 557
  • 5
  • 7
  • I changed the last line of validatevalue method to prevent adding a zero for invalid paste. if (valid) { this.hostElement.nativeElement['value'] = value;} – Abdul Rehman Sayed Oct 22 '18 at 11:08
  • 1
    can you please also add drag and drop validation too? Also, I noticed that the input fields value does change to the 0 padded value for leading and trailing decimal separator but the value doesn't update in the two way binding variable. for example: [(NgModel)]="myVariable" , here, if we type .3 in input field the value in text input changes to 0.3 on blur but the value in myVariable still remains '.3' . – Sushmit Sagar Mar 01 '19 at 06:52
  • Delete and Enter input is missing, but anyway solution is very good – Oleg Bondarenko Oct 08 '20 at 15:11
  • @SushmitSagar in order to make sure that 2 way data binding works add: @Output() ngModelChange:EventEmitter = new EventEmitter(); to the declarations and then add: this.ngModelChange.emit(valid ? value : 0); as the last line in validateValue method – Gabriel H Apr 16 '21 at 07:29
  • also add those lines to the validateValue method: //remove unnececary leading zeros let secondChar=value.charAt(1); if (firstCharacter=='0'&&(secondChar!=''||secondChar!=this.decimalSeparator)) { value=value.substring(1); } – Gabriel H Apr 16 '21 at 09:29
36

I would like to build on the answer given by @omeralper , which in my opinion provided a good foundation for a solid solution.

What I am proposing is a simplified and up to date version with the latest web standards. It is important to note that event.keycode is removed from the web standards, and future browser updates might not support it anymore. See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode

Furthermore, the method

String.fromCharCode(e.keyCode);

does not guarantee that the keyCode pertaining to the key being pressed by the user maps to the expected letter as identified on the user's keyboard, since different keyboard configurations will result in a particular keycode different characters. Using this will introduce bugs which are difficult to identify, and can easily break the functionality for certain users. Rather I'm proposing the use of event.key, see docs here https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key

Furthermore, we only want that the resultant output is a valid decimal. This means that the numbers 1, 11.2, 5000.2341234 should be accepted, but the value 1.1.2 should not be accepted.

Note that in my solution i'm excluding cut, copy and paste functionality since it open windows for bugs, especially when people paste unwanted text in associated fields. That would required a cleanup process on a keyup handler; which isn't the scope of this thread.

Here is the solution i'm proposing.

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

@Directive({
    selector: '[myNumberOnly]'
})
export class NumberOnlyDirective {
    // Allow decimal numbers. The \. is only allowed once to occur
    private regex: RegExp = new RegExp(/^[0-9]+(\.[0-9]*){0,1}$/g);

    // Allow key codes for special events. Reflect :
    // Backspace, tab, end, home
    private specialKeys: Array<string> = [ 'Backspace', 'Tab', 'End', 'Home' ];

    constructor(private el: ElementRef) {
    }

    @HostListener('keydown', [ '$event' ])
    onKeyDown(event: KeyboardEvent) {
        // Allow Backspace, tab, end, and home keys
        if (this.specialKeys.indexOf(event.key) !== -1) {
            return;
        }

        // Do not use event.keycode this is deprecated.
        // See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
        let current: string = this.el.nativeElement.value;
        // We need this because the current value on the DOM element
        // is not yet updated with the value from this event
        let next: string = current.concat(event.key);
        if (next && !String(next).match(this.regex)) {
            event.preventDefault();
        }
    }
}
JeanPaul A.
  • 3,613
  • 1
  • 20
  • 29
  • This is a really interesting approach. Do you have any suggestions on how to implement copy/paste functionality without resorting to older methods such as (e.keyCode == 67 && e.ctrlKey === true) ?? – Ender2050 Aug 18 '17 at 14:28
  • 1
    I haven't personally tried this, however you can similarly listen to the copy/paste events that are triggered. They generate a ClipboardEvent ( https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent ) which contains the data that is being copied/pasted. The only drawback is that this is still experimental and supported by the latest browsers only - https://caniuse.com/#search=paste – JeanPaul A. Aug 19 '17 at 10:15
  • I tried a similar approach but unfortunately this doesn't work for every case. Your "next" variable assumes that the pressed character is going at the end of the currently typed value. This isn't always the case. For example, if someone types in 100, and then decides to make it 1100 by appending a 1 to the front. Your "next" variable will be incorrect (1001). – Carlos Rodriguez Aug 23 '17 at 23:07
  • Since the 'next' value is only used to check if the input amount is a valid decimal (and not to set the value), appending it at the end would not change the regex validation. – JeanPaul A. Aug 24 '17 at 06:31
  • Only I would like add this line to apply in input control. – Lrodriguez84 Apr 25 '18 at 13:15
  • Also IE11 returns the string Decimal for event.key for the decimal key on the numeric keypad – pblack Nov 26 '18 at 16:34
  • This approach don't support negative numbers. – leox Jun 16 '19 at 04:42
  • Fantastic solution, well explained - understandable code and easy to modify. I added Arrow events to ```specialKeys``` and it's working like a charm. – KeaganFouche Aug 30 '22 at 20:32
28

A more concise solution. Try this directive.

Can also be used if you're using ReactiveForms.

export class NumberOnlyDirective {
  private el: NgControl;

  constructor(private ngControl: NgControl) {
    this.el = ngControl;
  }

  // Listen for the input event to also handle copy and paste.
  @HostListener('input', ['$event.target.value'])
  onInput(value: string) {
    // Use NgControl patchValue to prevent the issue on validation
    this.el.control.patchValue(value.replace(/[^0-9]/g, ''));
  }
}

The use it on your inputs like this:

<input matInput formControlName="aNumberField" numberOnly>
Ben Gulapa
  • 1,619
  • 14
  • 10
  • 6
    While this solution works it triggers model change events twice, that said the approach of using regex is the right one, here is a version that does NOT fire model changed events twice: https://stackblitz.com/edit/angular-numbers-only-directive?file=app%2Fnumbers-only.directive.ts – ntziolis Nov 06 '19 at 07:18
  • To ntziolis's comment: So far Ben Gulapa's solution is working for me. But the solution referenced by ntziolis is not. Forgive me if I am wrong, but it seems that the problem with the code at the above link to stackblitz, at least for me, was that the last unwanted character I typed, although it did not show ever in the user interface, somehow got put into the bound variable of my component. Just the last unwanted character. – user2367418 Feb 22 '20 at 00:49
  • To continue my comment: Using Angular 7 and an HMTL input text limited to two characters. – user2367418 Feb 22 '20 at 04:39
  • I can confirm there are double emissions with this solution. See a reproduction here: https://stackblitz.com/edit/angular-ivy-icq8we?file=src/app/app.module.ts – spierala Dec 13 '22 at 13:04
  • @ntziolis after testing your solution it doesnt works as the bound variable keeps letters characters. I will keep with Ben solution until the double emission is properly solved. – Sebastián Echeverry Feb 03 '23 at 19:10
21

You could use regex:

<input type="text" (keypress)="numericOnly($event)">

numericOnly(event): boolean {    
    let patt = /^([0-9])$/;
    let result = patt.test(event.key);
    return result;
}
Aathreya
  • 523
  • 4
  • 10
19

You need to use type="number" instead text. You can also specify max and min numbers

<input type="number" name="quantity" min="1" max="5">
Zia Khan
  • 223
  • 1
  • 3
  • 2
    I want to achieve this without using the number type. – Aniruddha Pondhe Jan 05 '17 at 06:32
  • 3
    Support for the number type is still quite buggy as described in this answer : http://stackoverflow.com/a/14995890/1156185 – Nicolas Forney May 16 '17 at 09:35
  • 12
    The downside of `type="number"` is that it accepts character `e` as part of scientific notation – user776686 Jun 16 '17 at 10:18
  • 1
    The downside of the type="number" is that it breaks the keyboard and sends the user guessing why the keyboard stopped working. Filtering is very anti user friendly, a better approach is to show an clear error message and keep the input intact. When a user enters a1b2c3 it makes absolutely no sense to give him the result "123" – Cesar Jun 06 '21 at 15:53
19
<input type="text" (keypress)="keyPress($event)">


  keyPress(event: any) {
    const pattern = /[0-9\+\-\ ]/;

    let inputChar = String.fromCharCode(event.charCode);
    if (event.keyCode != 8 && !pattern.test(inputChar)) {
      event.preventDefault();
    }
  }
ketan pradhan
  • 588
  • 2
  • 6
  • 13
15

you can achive it like this

<input type="text" pInputText (keypress)="onlyNumberKey($event)" maxlength="3"> 

onlyNumberKey(event) {
    return (event.charCode == 8 || event.charCode == 0) ? null : event.charCode >= 48 && event.charCode <= 57;
}

//for Decimal you can use this as

onlyDecimalNumberKey(event) {
    let charCode = (event.which) ? event.which : event.keyCode;
    if (charCode != 46 && charCode > 31
        && (charCode < 48 || charCode > 57))
        return false;
    return true;
}

hope this will help you.

Pardeep Jain
  • 84,110
  • 37
  • 165
  • 215
12

I know this is has a lot of answers, but I needed to handle the following (which none of the answers seemed to fully support):

  • Support of textarea with option to for multi-lines
  • Decimals or negative numbers
  • Max length per line
  • Cross-browser support (Chrome, Edge, IE 11)
  • Handling cut/paste operations and events

The solution allows me to define a textarea like this:

<textarea class="form-control" [(ngModel)]="this.myModelVariable"
    appOnlyNumbers [allowNegative]="true" [allowMultiLine]="true" 
    [allowDecimal]="true" [maxLength]="10"
    placeholder="Enter values (one per line)"></textarea>

Or if I just want positive integers

<textarea class="form-control" [(ngModel)]="this.myModelVariable"
    appOnlyNumbers [allowMultiLine]="true" [maxLength]="9"
    placeholder="Enter values (one per line)"></textarea>

Here is my directive:

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

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

  @Input() allowMultiLine: boolean = false;
  @Input() allowNegative: boolean = false;
  @Input() allowDecimal: boolean = false;
  @Input() maxLength: number = 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 = (<any>window).clipboardData && (<any>window).clipboardData.getData('Text') // If IE, use window
      || <ClipboardEvent>event && (<ClipboardEvent>event).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) {
      this.regex = <RegExp>eval('/^'
        + (this.allowNegative ? '-?' : '')
        + (this.allowDecimal ? '((\\d+\\.?)|(\\.?))\\d*' : '\\d*')
        + '$/g');
    }
    var lines = this.allowMultiLine ? newValue.split('\n') : [newValue];
    for (let line of lines) {
      let lineText = line.replace('\r', '');
      if (this.maxLength && lineText.length > this.maxLength || !lineText.match(this.regex)) {
        event.preventDefault();
        return;
      }
    }
  }

}
Jason W
  • 13,026
  • 3
  • 31
  • 62
  • Nice, Thank you mate ^_^ – Leebeedev Feb 06 '21 at 17:45
  • I tried to modify this answer for my needs (no need for multi-line) only to realize that it fails for `input type="number"` because selectionStart/End are not available for it. In fact, there seems to be no way to get the caret position inside a number input :( – AsGoodAsItGets Feb 16 '21 at 15:33
8

A modern approach for the best answer (without deprecated e.keyCode):

@HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if (['Delete', 'Backspace', 'Tab', 'Escape', 'Enter', 'NumLock', 'ArrowLeft', 'ArrowRight', 'End', 'Home', '.'].indexOf(e.key) !== -1 ||
      // Allow: Ctrl+A
      (e.key === 'a' && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+C
      (e.key === 'c' && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+V
      (e.key === 'v' && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+X
      (e.key === 'x' && (e.ctrlKey || e.metaKey))) {
      // let it happen, don't do anything
      return;
    }
    // Ensure that it is a number and stop the keypress
    if ((e.shiftKey || ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].indexOf(e.key) === -1)) {
      e.preventDefault();
    }
}
Agorreca
  • 684
  • 16
  • 31
  • 2
    This is awesome! @Directive({ selector: "[inputNumericInput]" }) export class NumericInputDirective { @HostListener() } – Nate Nov 29 '19 at 12:08
  • 3
    Works well. Only side effect observed in copy paste. It allows copy paste of external non numeric strings. Googled and found a better solution which addresses this @ https://stackblitz.com/edit/angular-numbers-only-directive?file=app%2Fnumbers-only.directive.ts – vinsinraw Apr 17 '20 at 11:15
7

Use pattern attribute for input like below:

<input type="text" pattern="[0-9]+" >
Behnam Azimi
  • 2,260
  • 3
  • 34
  • 52
7
  1. <input oninput="this.value=this.value.replace(/[^0-9]/g,'')"

or: 2. in The HTML File :

 <input [(ngModel)]="data" (keypress)="stripText($event)"
     class="form-control">

in The ts File:

stripText(event) {
const seperator  = '^([0-9])';
const maskSeperator =  new RegExp(seperator , 'g');  
let result =maskSeperator.test(event.key);   return result;   }

This 2 solution works

abolfazl_mehdi
  • 179
  • 2
  • 12
6

Well Thanks to JeanPaul A. and rdanielmurphy. I had made my own Custom directive for limiting input field to number only. Also added the max and min input attributes. Will work in angular 7 also.

Angular

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

@Directive({
  selector: '[appNumberOnly]'
})
export class NumberOnlyDirective {
  // Allow decimal numbers. The \. is only allowed once to occur
  private regex: RegExp = new RegExp(/^[0-9]+(\.[0-9]*){0,1}$/g);

  // Allow key codes for special events. Reflect :
  // Backspace, tab, end, home
  private specialKeys: Array<string> = ['Backspace', 'Tab', 'End', 'Home'];
  constructor(private el: ElementRef) { }

  @Input() maxlength: number;
  @Input() min: number;
  @Input() max: number;

  @HostListener('keydown', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    // Allow Backspace, tab, end, and home keys
    if (this.specialKeys.indexOf(event.key) !== -1) {
      return;
    }

    // Do not use event.keycode this is deprecated.
    // See: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
    const current: string = this.el.nativeElement.value;

    // We need this because the current value on the DOM element
    // is not yet updated with the value from this event
    const next: string = current.concat(event.key);
    if (next && !String(next).match(this.regex) || (this.maxlength && next.length > this.maxlength) ||
      (this.min && +next < this.min) ||
      (this.max && +next >= this.max)) {
      event.preventDefault();
    }
  }

  @HostListener('paste', ['$event']) onPaste(event) {
    // Don't allow pasted text that contains non-numerics
    const pastedText = (event.originalEvent || event).clipboardData.getData('text/plain');

    if (pastedText) {
      const regEx = new RegExp('^[0-9]*$');
      if (!regEx.test(pastedText) || (this.maxlength && pastedText.length > this.maxlength) ||
        (this.min && +pastedText < this.min) ||
        (this.max && +pastedText >= this.max)) {
        event.preventDefault();
      }
    }
  }

}

HTML

<input type="text" class="text-area" [(ngModel)]="itemName" maxlength="3" appNumberOnly />
leox
  • 1,315
  • 17
  • 26
  • This assumes that the cursor/caret is always at the end of the typed number. If the user wants to type somewhere in between, it fails. – AsGoodAsItGets Feb 16 '21 at 16:01
5

Arbitrary RegExp directive

Here is small directive which use arbitrary regexp and block user to type invalid value

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

@Directive({selector: '[allowedRegExp]'})
export class AllowedRegExpDirective {
    
  @Input() allowedRegExp: string;
  
  @HostListener('keydown', ['$event']) onKeyDown(event: any) {
    // case: selected text (by mouse) - replace it
    let s= event.target.selectionStart;
    let e= event.target.selectionEnd;
    let k= event.target.value + event.key;
        
    if(s!=e) {
      k= event.target.value
      k= k.slice(0,s) + event.key + k.slice(e,k.length);
    }

    // case: special characters (ignore)
    if(['ArrowLeft','ArrowRight','ArrowUp','ArroDown','Backspace','Tab','Alt'
       'Shift','Control','Enter','Delete','Meta'].includes(event.key)) return;

    // case: normal situation - chceck regexp
    let re = new RegExp(this.allowedRegExp);
        
    if(!re.test(k)) event.preventDefault();
  }
}

To mask only numbers use

<input [allowedRegExp]="'^[0-9]*$'" type="text" ... >
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
4

Just Create a directive and add below hostlistener:

@HostListener('input', ['$event'])
    onInput(event: Event) {
        this.elementRef.nativeElement.value = (<HTMLInputElement>event.currentTarget).value.replace(/[^0-9]/g, '');
    }

Replace invalid text with empty. All keys and key combinations will now work across all browsers till IE9.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Gaurav Joshi
  • 482
  • 3
  • 9
  • If start type with char, char will not append but count of the model length takes 1. How to solve that?. Also if element has a max length, then copy and paste mixed content, model count would be max length. For example, max length have 10, then copy and paste 1238261jhgjh12987 to input will append 123816 only but the length of the model would take 10. Any solutions? – Satheesh Natarajan Apr 11 '18 at 12:14
4

In order to accomplish this, I bound a function to the onInput method like this:

(input)="stripText(infoForm.get('uin'))

Here is the example inside my form:

<form [formGroup]="infoForm" (submit)="next()" class="ui form">
    <input type="text" formControlName="uin" name="uin" id="uin" (input)="stripText(infoForm.get('uin'))" required/>
</form>

Then I added the following function to my component:

  stripText(control: FormControl) {
   control.setValue(control.value.replace(/[^0-9]/g, ''));
  }

This regex /[^0-9]/g searches for anything that is not a number and using .replace I set it to be replaced by nothing. So when a user tries to type in a character that is not a number (in this case a character that is not zero through nine), it appears as if nothing happens in the text box.

Christopher
  • 1,019
  • 1
  • 7
  • 12
  • the input type must be of type "text" for this to work. If the input type is "number" the control.value is "" in the dom. – gcerkez Apr 10 '23 at 18:29
3

Here is easy one: Simple directive On keydown event it checks the length of a key is one and key is not a number to preventDefault() and it won't renders that char.

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

@Directive({
    selector: '[numbersOnly]'
})
export class NumbersOnlyDirective {
    @HostListener('keydown', ['$event'])
    keyDownEvent(event: KeyboardEvent) {
        if (event.key.length === 1 && (event.which < 48 || event.which > 57)) {
            event.preventDefault();
        }
    }

}

HTML:

<input type="text" [(ngModel)]="numModel" numbersOnly />

Limitations: It will allow pasting using a mouse that way will accept other char. To avoid that you can pass the model as input to the directive and ngOnChage to that model change value to only numbers:

Like below:

EDIT: Added Code to detect change in Model and update the input's value

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

@Directive({
    selector: '[numbersOnly]'
})
export class NumbersOnlyDirective implements OnChanges {

    @Input() numbersOnly: any;

    constructor(private el: ElementRef) {}

    @HostListener('keydown', ['$event'])
    keyDownEvent(event: KeyboardEvent) {
        // Add other conditions if need to allow ctr+c || ctr+v
        if (event.key.length === 1 && (event.which < 48 || event.which > 57)) {
            event.preventDefault();
        }
    }

    ngOnChanges(changes) {
        if (changes.numbersOnly) {
            this.el.nativeElement.value = this.el.nativeElement.value.replace(/[^0-9]/g, '');
        }
    }

}

HTML:

<input type="text" [(ngModel)]="numModel" [numbersOnly]="numModel" />
Lahar Shah
  • 7,032
  • 4
  • 31
  • 39
3

Pattern for the Valid Mobile number pattern('^((\+91-?)|0)?[0-9]{10}$')

Pattern for accept only number from text box pattern('[0-9]*')

patter for accept only number with specific number e.g: Pincode. pattern('^[0-9]{5}$')

Satish Deokar
  • 201
  • 1
  • 9
3

You can do this easily using a mask:

<input type='text' mask="99" formControlName="percentage" placeholder="0">

99 - optional 2 digits

Don't forget to import NgxMaskModule in your module:

imports: [
    NgxMaskModule.forRoot(),
]
Buzzy
  • 1,783
  • 18
  • 15
2

I have made some modifications in the above directive and implemented min, max, maxlength.

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

@Directive({
  selector: '[numberOnly]'
})
export class NumbersOnlyDirective {

  private regex: RegExp = new RegExp(/[0-9]/g);
  // Allow key codes for special events. Reflect :
  private specialKeys: Array<number> = [46, 8, 9, 27, 13, 110, 190, 35, 36, 37, 39];
  // Backspace, tab, end, home

  @Input() maxlength: number;
  @Input() min: number;
  @Input() max: number;

  constructor(private el: ElementRef) {
  }
    @HostListener('keydown', ['$event'])
    onKeyDown(event: KeyboardEvent) {
    e = <KeyboardEvent>event;

if ((
  (this.specialKeys.indexOf(event.which) > -1) ||
  // to allow backspace, enter, escape, arrows  
  (e.which == 65 && e.ctrlKey == true) ||
  // Allow: Ctrl+C        
  (e.which == 67 && e.ctrlKey == true) ||
  // Allow: Ctrl+X
  (e.which == 88 && e.ctrlKey == true))) {
  return;
} else if (// to allow numbers  
  (e.which >= 48 && e.which <= 57) ||
  // to allow numpad number  
  (event.which >= 96 && event.which <= 105)) { }
else {
      event.preventDefault();
    }
    let current: string = this.el.nativeElement.value;

    let next: string = current.concat(event.key);
    if ((next && !String(next).match(this.regex)) ||
      (this.maxlength && next.length > this.maxlength) ||
      (this.min && +next < this.min) ||
      (this.max && +next >= this.max)) {
      event.preventDefault();
    }

  }
}
2

You could also create a directive which implements the ControlValueAccessor Interface (https://angular.io/api/forms/ControlValueAccessor).

See working example here: https://stackblitz.com/edit/angular-input-field-to-accept-only-numbers

You can listen to the 'input' event and there is no need to check for keycodes. It supports copy & paste and integrates nicely with the Angular Forms API due to the ControlValueAccessor Interface.

Directive:

@Directive({
    ...
    selector: '[onlyNumber]'
})
export class OnlyNumberDirective implements ControlValueAccessor {
private onChange: (val: string) => void;
...
private value: string;

constructor(
    private elementRef: ElementRef,
    private renderer: Renderer2
) {
}

...

@HostListener('input', ['$event.target.value'])
onInputChange(value: string) {
    const filteredValue: string = filterValue(value);
    this.updateTextInput(filteredValue, this.value !== filteredValue);
}

private updateTextInput(value, propagateChange) {
    this.renderer.setProperty(this.elementRef.nativeElement, 'value', value);
    if (propagateChange) {
        this.onChange(value);
    }
    this.value = value;
}

// ControlValueAccessor Interface
...

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

writeValue(value: string): void {
    value = value ? String(value) : '';
    this.updateTextInput(value, false);
}
}


function filterValue(value): string {
    return value.replace(/[^0-9]*/g, '');
}

Usage:

<input name="number" type="text" onlyNumber [(ngModel)]="someNumber">
spierala
  • 2,349
  • 3
  • 25
  • 50
2

Casting because it works also with leading 0 like 00345

@Directive({
  selector: '[appOnlyDigits]'
})
export class AppOnlyDigitsDirective {
  @HostListener('input', ['$event'])
  onKeyDown(ev: KeyboardEvent) {
    const input = ev.target as HTMLInputElement;
    input.value = String(input.value.replace(/\D+/g, ''));
  }
}
Whisher
  • 31,320
  • 32
  • 120
  • 201
2

While there are more than a few answers, none comes as a simple npm package

Based on Elvis Fernandez's answer, and after i added handling for a couple of edge cases I created an NG module that you can easly install using npm:

npm i ngx-numbers-only-directive

How To Use:

In your appModule Import NgxNumbersOnlyDirectiveModule:

import { NgxNumbersOnlyDirectiveModule } from 'ngx-numbers-only-directive'

and add it to your imports array:

imports: [NgxNumbersOnlyDirectiveModule]

Add the directive to an input element. examples:

< input NgxNumbersOnly >

To allow Negative numbers:

<input NgxNumbersOnly [allowSign]=true>

To allow decimals:

<input NgxNumbersOnly [allowDecimals]=true [allowSign]=true>

if you do not wish to add the package, the directive source code is at :

https://github.com/abfist/NgxNumbersOnlyDirective/tree/master/projects/ngx-numbers-only-directive/src/lib

Gabriel H
  • 1,558
  • 2
  • 14
  • 35
1

from @omeralper 's answer. I change a little bit that won't accept period ascii (keycode 110,190). and use let ch = (e.key); to compare with regular expression when you change language (such as Thai or Japanese language) it won't accept character of those language

export class OnlyNumber {

  regexStr = '^[0-9]*$';
  constructor(private el: ElementRef) { }

  @Input() OnlyNumber: boolean;

  @HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if (this.OnlyNumber) {
      // console.log(event, this.OnlyNumber);
        if ([46, 8, 9, 27, 13].indexOf(e.keyCode) !== -1) {
          return;
        }
      let ch = (e.key);
      let regEx =  new RegExp(this.regexStr);   
      if(regEx.test(ch))
        return;
      else
         e.preventDefault();
    }
  }
}

hope this help :)

1

You can create this Validator and import it in your component.
Basically validates the form input string:

  • check there is no dot
  • converts string to number
  • check is an integer
  • check is greater than zero

To implement it in your project:

  1. suggested path in your app folder: src/app/validators/number.validator.ts
  2. import in your component

    import { NumberValidator } from '../../validators/number.validator';

  3. add it to the form control
    inputNumber: ['', [NumberValidator.isInteger]],
  4. if you dont want to show the invalid char, bind a (change)="deleteCharIfInvalid()" to the input, if form.get('inputNumber').hasError('isInteger') is true, delete the last char inserted.
// FILE: src/app/validators/number.validator.ts

import { FormControl } from '@angular/forms';

export interface ValidationResult {
    [key: string]: boolean;
}

export class NumberValidator {

    public static isInteger(control: FormControl): ValidationResult {
        // check if string has a dot
        let hasDot:boolean = control.value.indexOf('.') >= 0 ? true : false;
        // convert string to number
        let number:number = Math.floor(control.value);
        // get result of isInteger()
        let integer:boolean = Number.isInteger(number);
        // validate conditions 
        let valid:boolean = !hasDot && integer && number>0;
        console.log('isInteger > valid', hasDot, number, valid);
        if (!valid) {
            return { isInteger: true };
        }
        return null;
    }        
}
guillefd
  • 1,903
  • 1
  • 16
  • 26
1

With support for sanitizing pasted content:

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

@Directive({
  selector: '[NumbersOnly]'
})
export class NumbersOnlyDirective {

    DIGITS_REGEXP =  new RegExp(/\D/g);
    constructor(private el: ElementRef) { 

        // Sanatize clipboard by removing any non-numeric input after pasting
        this.el.nativeElement.onpaste = (e:any) => {
            e.preventDefault();
            let text;
            let clp = (e.originalEvent || e).clipboardData;
            if (clp === undefined || clp === null) {
                text = (<any>window).clipboardData.getData('text') || '';
                if (text !== '') {
                    text = text.replace(this.DIGITS_REGEXP, '');
                    if (window.getSelection) {
                        let newNode = document.createElement('span');
                        newNode.innerHTML = text;
                        window.getSelection().getRangeAt(0).insertNode(newNode);
                    } else {
                        (<any>window).selection.createRange().pasteHTML(text);
                    }
                }
            } else {
                text = clp.getData('text/plain') || '';
                if (text !== '') {
                    text = text.replace(this.DIGITS_REGEXP, '');
                    document.execCommand('insertText', false, text);
                }
            }
        };
    }

  @HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if ([46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 ||
      // Allow: Ctrl+A
      (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+C
      (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+V
      (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+X
      (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) ||
      // Allow: home, end, left, right
      (e.keyCode >= 35 && e.keyCode <= 39)) {
        // let it happen, don't do anything
        return;
      }
      // Ensure that it is a number and stop the keypress
      if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
          e.preventDefault();
      }
    }

}
mad_fox
  • 3,030
  • 5
  • 31
  • 43
1

Just use type number on your text like below:

<input type="number" class="form-control" matInput name="value" placeholder="xxx" (change)="xxx()" formControlName="value">
Abdus Salam Azad
  • 5,087
  • 46
  • 35
1
 import {Directive, ElementRef, HostListener, Output, EventEmitter} from '@angular/core';


    //only-digits
    @Directive({
      selector: '[only-digits]'
    })
    export class OnlyDigits {

      constructor(public el: ElementRef) {

        this.el.nativeElement.onkeypress = (evt) => {
          if (evt.which < 48 || evt.which > 57) {
            evt.preventDefault();
          }
        };

      }
    }

Directive is also a best way to do it

1

Ngx Mask has a great solution. It's a light package to install so it is worth it. I use this when using type="number" is not a desired option. Once installed all you have to do is: <input type="text" mask="separator.2" thousandSeparator="," />

Mike
  • 61
  • 1
  • 3
  • Also: https://stackoverflow.com/questions/68732277/number-pipe-taking-only-three-digits-before-decimal/68738338#68738338 – Eliseo Aug 12 '21 at 07:15
0

fromCharCode returns 'a' when pressing on the numpad '1' so this methoid should be avoided

(admin: could not comment as usual)

Phil
  • 708
  • 1
  • 11
  • 22
0

I saw a lot of comments about handling copy/pasting.

To piggy back off of @omeralper answer, you can add a paste event handler to the onlyNumber directive to handle copy/pasting:

 @HostListener('paste', ['$event']) onPaste(event) {
  // Don't allow pasted text that contains non-numerics
  var pastedText = (event.originalEvent || event).clipboardData.getData('text/plain');

  if (pastedText) {
    var regEx = new RegExp('^[0-9]*$');
    if (!regEx.test(pastedText)) {
      event.preventDefault();
    }
}

This will only allow content to be copy and pasted into the textbox ONLY if it is a number. That's the simplest solution. Changing the content of the clipboard to remove non-numerics is a lot more complicated and might not be worth it.

To get pasted text from IE you can use the following:

window.clipboardData.getData('Text');
0

If you use primeng and Angular 6 or above there is the p-inputMask component. It prevents alpha typing AND negative values https://www.primefaces.org/primeng/#/inputmask

Dotista
  • 404
  • 3
  • 12
0

Would not be simple enough just to write

onlyNumbers(event) {
if(isNaN(event.target.value * 1)) {
 console.log("Not a number")
} else {
  console.log("Number")
}

}

0
<input type="number" min="0" oninput="this.value = Math.abs(this.value)">
Sandeep Patel
  • 2,069
  • 2
  • 14
  • 20
0

HTML:

<input type="text" maxlength="5"(focusout)="onTextboxFocusOutDecimalPercentageValidate($event)" 
(input)="onTextboxChangeDecimalPercentageValidate($event)">

TS:

onTextboxChangeDecimalPercentageValidate(event: Event) {

    var inputData = (<HTMLInputElement>event.target).value;

    //replace more than one dot

    var extractedFte = inputData.replace(/[^0-9.]/g, '').replace('.', 'x')

                      .replace(/\./g, '').replace('x', '.');

    //Extract Decimal Values

    extractedFte = extractedFte.replace(/^(\d+.?\d{0,2})\d*$/, "$1");

    //Reasign to same control

    (<HTMLInputElement>event.target).value = extractedFte;

    if (extractedFte != '' && Number(extractedFte) >= 100) {

      (<HTMLInputElement>event.target).value = '100'; extractedFte = '100';

    }

    // if (Number(extractedFte) == 0) {

    //   (<HTMLInputElement>event.target).value = ''; extractedFte = '';

    // }

  }

  onTextboxFocusOutDecimalPercentageValidate(event: Event) {

    var inputData = (<HTMLInputElement>event.target).value;

    //replace more than one dot

    var extractedFte = inputData.replace(/[^0-9.]/g, '').replace('.', 'x')

                      .replace(/\./g, '').replace('x', '.');

    //Extract Decimal Values

    extractedFte = extractedFte.replace(/^(\d+.?\d{0,2})\d*$/, "$1");

    //Reasign to same control

    (<HTMLInputElement>event.target).value = extractedFte;

    if (extractedFte != '' && Number(extractedFte) >= 100) {

      (<HTMLInputElement>event.target).value = '100'; extractedFte = '100';

    }

    if (Number(extractedFte) == 0) {

      (<HTMLInputElement>event.target).value = ''; extractedFte = '';

    }

  }
THess
  • 1,003
  • 1
  • 13
  • 21
ArulKumar
  • 39
  • 5
0

You could use @ngneat/input-mask

It is easy like that:

TypeScript:

onlyNumbersMask = createMask<string>({
    regex: "\\d*",
});

Template:

<input[(ngModel)]="value" [inputMask]="onlyNumbersMask">

StackBlitz demo: https://stackblitz.com/edit/input-mask-only-numbers-ngneat-input-mask?file=src/app/app.component.ts

spierala
  • 2,349
  • 3
  • 25
  • 50
-1

Use directive to restrict the user to enter only numbers in the following way:

.directive('onlyNumber', function () {
    var regExp = /^[0-9]*$/;
    return {
        require: '?ngModel',
        restrict: 'A',
        priority: 1,
        link: function (scope, elm, attrs, ctrl) {
            ctrl.$validators.onlyNumber= function (modalValue) {
                return ctrl.$isEmpty(modalValue) || regExp.test(modalValue);
            };
        }
    };
    })

In HTML:

<input id="txtRollNumber" type="text" name="rollNumber" placeholder="Enter roll number*" ng-model="rollNumber" class="form-control" maxlength="100" required only-number />

Angular2:

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

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

  constructor(private el: ElementRef) { }

  @Input() OnlyNumber: boolean;

  @HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    if (this.OnlyNumber) {
      if ([46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 ||
        // Allow: Ctrl+A
        (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+C
        (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+V
        (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) ||
        // Allow: Ctrl+X
        (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) ||
        // Allow: home, end, left, right
        (e.keyCode >= 35 && e.keyCode <= 39)) {
          // let it happen, don't do anything
          return;
        }
        // Ensure that it is a number and stop the keypress
        if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
            e.preventDefault();
        }
      }
  }
}

And need to write the directive name in your input as an attribute.

<input OnlyNumber="true" />
Jayoti Parkash
  • 868
  • 11
  • 26
-2

Below is my angular code that allows the only number to enter and only paste number, not text.

<input id="pId" maxlength="8" minlength="8" type="text" [(ngModel)]="no" formControlName="prefmeno" name="no" class="form-control">

And in ts file added in ngOnIt.

ngOnInit() {
  setTimeout(() => {
  jQuery('#pId').on('paste keyup', function(e){
    jQuery(this).val(document.getElementById('pId').value.replace(/[^\d]/g, ''));
  });
}, 2000);
}

I used setTimeout for waiting time to load DOM. And used jquery with javascript to perform this task. 'Paste' and 'keyup' are used to trigger paste and enter in the field.

ajay hariyal
  • 243
  • 2
  • 9
-5

Just use HTML5, input type=”number”

San Jaisy
  • 15,327
  • 34
  • 171
  • 290
  • the problem comes , when you try to have the same behavior in all browser (this case only numbers, I see the input behaves different chrome vs firefox) – qleoz12 Jul 29 '20 at 19:01