Angular2 - Input Field To Accept Only Numbers

343,661

Solution 1

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();
      }
  }
}

Solution 2

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;

  }
}

Solution 3

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">

Solution 4

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();
        }
    }
}

Solution 5

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>
Share:
343,661

Related videos on Youtube

Aniruddha Pondhe
Author by

Aniruddha Pondhe

UI/UX Ninja &amp; Front End Developer. Enjoy turning complex problems into simple, beautiful and intuitive interface designs. 2.5+ years of professional experience in Application Development Framework (ADF), Frontend Development and Mobile Design. Proficient in HTML, CSS3, JavaScript, jQuery, Bootstrap, Responsive Design and MVC. Pixel-perfect attention-to-detail, highly organized, strong communication skills and a team-player. When I am not coding, you will find me in the kitchen cooking up something new!

Updated on August 12, 2021

Comments

  • Aniruddha Pondhe
    Aniruddha Pondhe almost 3 years

    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.

    • inoabrian
      inoabrian over 7 years
      Would you be able to just use the html attribute? type=number
    • Aniruddha Pondhe
      Aniruddha Pondhe over 7 years
      @inoabrian I want to achieve this without using the number type.
    • chandan7
      chandan7 over 7 years
  • Aniruddha Pondhe
    Aniruddha Pondhe over 7 years
    I want to achieve this without using the number type.
  • Aniruddha Pondhe
    Aniruddha Pondhe over 7 years
    That's great. Any way I can achieve the same using RegEx patterns?
  • Al-Mothafar
    Al-Mothafar about 7 years
    @Shardul just add (e.keyCode == 86 && e.ctrlKey === true) to conditions, copy is working but paste was not working
  • Nicolas Forney
    Nicolas Forney about 7 years
    Support for the number type is still quite buggy as described in this answer : stackoverflow.com/a/14995890/1156185
  • user776686
    user776686 almost 7 years
    The downside of type="number" is that it accepts character e as part of scientific notation
  • bosari
    bosari almost 7 years
    could you elaborate on this? what does event.charCode==8 is doing?
  • daniel
    daniel almost 7 years
    I added e.keyCode == 109 || e.keyCode ==190 for . and - but - is not accepted?
  • Himanshu Modi
    Himanshu Modi over 6 years
    it works great .. but it allows non numeric when you paste using mouse.. so probably that mouse event can be handled separately. Thanks omeralper
  • Ender2050
    Ender2050 over 6 years
    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) ??
  • JeanPaul A.
    JeanPaul A. over 6 years
    I haven't personally tried this, however you can similarly listen to the copy/paste events that are triggered. They generate a ClipboardEvent ( 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 - caniuse.com/#search=paste
  • Carlos Rodriguez
    Carlos Rodriguez over 6 years
    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).
  • JeanPaul A.
    JeanPaul A. over 6 years
    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.
  • macha devendher
    macha devendher over 6 years
    Can you please explain me how did you handle paste ,because it accepting all strings also @Himanshu Modi
  • eatinasandwich
    eatinasandwich over 6 years
    It's in other answers, but to blind copy-pasters like myself you should use event.keyCode ? event.keyCode : event.which to accommodate firefox because keyCode comes through as 0.
  • VR1256
    VR1256 over 6 years
    the solution provided is not working when we do mouse right click copy and then paste
  • AndyTheEntity
    AndyTheEntity over 6 years
    Won't Number.isInteger(Math.floor(control.value)) be always true? I think it should be parseFloat instead.
  • Dilshan Liyanage
    Dilshan Liyanage about 6 years
    The question is about Angular 2, not angularJS
  • Albert-Jan Verhees
    Albert-Jan Verhees about 6 years
    You can add the Mac Command key by checking e.metaKey (e.keyCode == 86 && (e.ctrlKey === true || e.metaKey === true))
  • Satheesh Natarajan
    Satheesh Natarajan about 6 years
    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
    Satheesh Natarajan about 6 years
    If start type with char, char will not append but count of the model length takes 1. How to solve that?
  • Lahar Shah
    Lahar Shah about 6 years
    when do you check for the length, it remains 0 in the directive before and after making changes. If at some point it's one it should come back to 0 quickly.
  • Satheesh Natarajan
    Satheesh Natarajan about 6 years
    No it's not. Just try to bind numModel.length in template and check the length count
  • Lrodriguez84
    Lrodriguez84 about 6 years
    Only I would like add this line to apply in input control. <input myNumberOnly type="text" id="yourId">
  • Jason Brody
    Jason Brody almost 6 years
    how to give max length value from input field
  • Jason Brody
    Jason Brody almost 6 years
    <input id="COMN" class="wb-e-inp-1__input" type="text" appNumberOnly maxlength="10"/> working
  • Darryn Hosking
    Darryn Hosking over 5 years
    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.
  • Abdul Rehman Sayed
    Abdul Rehman Sayed over 5 years
    I changed the last line of validatevalue method to prevent adding a zero for invalid paste. if (valid) { this.hostElement.nativeElement['value'] = value;}
  • pblack
    pblack over 5 years
    Also IE11 returns the string Decimal for event.key for the decimal key on the numeric keypad
  • Sushmit Sagar
    Sushmit Sagar about 5 years
    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' .
  • leox
    leox almost 5 years
    This approach don't support negative numbers.
  • Raja Rama Mohan Thavalam
    Raja Rama Mohan Thavalam almost 5 years
  • Rinku Choudhary
    Rinku Choudhary over 4 years
    yes it is helpful but I want (.)decimal also in my input field
  • ntziolis
    ntziolis over 4 years
    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: stackblitz.com/edit/…
  • Nate
    Nate over 4 years
    This is awesome! @Directive({ selector: "[inputNumericInput]" }) export class NumericInputDirective { @HostListener() }
  • user2367418
    user2367418 about 4 years
    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
    user2367418 about 4 years
    To continue my comment: Using Angular 7 and an HMTL input text limited to two characters.
  • vinsinraw
    vinsinraw about 4 years
    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 @ stackblitz.com/edit/…
  • qleoz12
    qleoz12 almost 4 years
    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)
  • YuS
    YuS over 3 years
    Please use code blocks to format your code snippets.
  • Seyed-Amir-Mehrizi
    Seyed-Amir-Mehrizi over 3 years
    it is not working. when you start to type, you type characters that it is wrong
  • ProgFroz
    ProgFroz over 3 years
    Unfortunantely you can trick this solution by spamming circumflex + whatever you want to write.
  • Oleg Bondarenko
    Oleg Bondarenko over 3 years
    Delete and Enter input is missing, but anyway solution is very good
  • Leebeedev
    Leebeedev over 3 years
    Nice, Thank you mate ^_^
  • AsGoodAsItGets
    AsGoodAsItGets about 3 years
    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
    AsGoodAsItGets about 3 years
    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.
  • Gabriel H
    Gabriel H about 3 years
    @SushmitSagar in order to make sure that 2 way data binding works add: @Output() ngModelChange:EventEmitter<any> = new EventEmitter(); to the declarations and then add: this.ngModelChange.emit(valid ? value : 0); as the last line in validateValue method
  • Gabriel H
    Gabriel H about 3 years
    also add those lines to the validateValue method: //remove unnececary leading zeros let secondChar=value.charAt(1); if (firstCharacter=='0'&&(secondChar!=''||secondChar!=this.deci‌​malSeparator)) { value=value.substring(1); }
  • Cesar
    Cesar almost 3 years
    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"
  • Eliseo
    Eliseo almost 3 years