Skip to content

Instantly share code, notes, and snippets.

@ahmeti
Last active April 23, 2024 17:47
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save ahmeti/5ca97ec41f6a48ef699ee6606560d1f7 to your computer and use it in GitHub Desktop.
Save ahmeti/5ca97ec41f6a48ef699ee6606560d1f7 to your computer and use it in GitHub Desktop.
Angular 5 - Only Number Input, Only Number Decimal

Angular 5,6,7,8,9 - Only Number Input, Only Decimal Input

Online Demo: https://codesandbox.io/s/purple-monad-ljwlb?fontsize=14&hidenavigation=1&module=%2Fsrc%2Fapp%2Fnumeric.directive.ts&theme=dark

Allow Only Numbers (Without Decimal)

<input numeric type="text">

Allow Numbers & Only Two Decimals [0-9] (With Decimal Limit)

<input numeric decimals="2" type="text">
import {Directive, ElementRef, HostListener, Input} from '@angular/core';

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

export class NumericDirective {

    @Input('decimals') decimals: int = 0;

    private check(value: string, decimals: int)
    {
      if (decimals <= 0) {
        return String(value).match(new RegExp(/^\d+$/));
      } else {
          var regExpString = "^\\s*((\\d+(\\.\\d{0," + decimals + "})?)|((\\d*(\\.\\d{1," + decimals + "}))))\\s*$"
          return String(value).match(new RegExp(regExpString));
      }
    }

    private specialKeys = [ 
      'Backspace', 'Tab', 'End', 'Home', 'ArrowLeft', 'ArrowRight', 'Delete'
    ];

    constructor(private el: ElementRef) {
    }

    @HostListener('keydown', [ '$event' ])
    onKeyDown(event: KeyboardEvent) {
        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;
        let next: string = current.concat(event.key);
        if ( next && !this.check(next, this.decimals) ) {
           event.preventDefault();
        }
    }
}
@rick2ricks
Copy link

rick2ricks commented Nov 8, 2019

Thanks.

I also added the following changes so I can edit the number at any place in the input:

let current: string = this.el.nativeElement.value;
let position: number = this.el.nativeElement.selectionStart;
let next: string = [current.slice(0, position), event.key, current.slice(position)].join('');

I would also suggest to check condition on Backspace and Delete.

@zeeshan4418
Copy link

zeeshan4418 commented Nov 11, 2019

it's not working fine when i am entering text this field accepting text

@Jozx
Copy link

Jozx commented Dec 6, 2019

It's work fine.

I have a question, if I need put a variable inside, how could I do?

I try

x:number = 0;
 y:number = 2;
private regex = {
        number: new RegExp(/^\d+$/),
        decimal : new RegExp("^\\d{"+ x +","+ y +"}$", "g")
    };

also
decimal: new RegExp(/^\d*\,?\d{"+ x +","+ y +"}$/g)

but didn't work

Any suggestion? Because I need use that like a parameter (the user put how many decimal want to use)

@ahmeti
Copy link
Author

ahmeti commented Dec 11, 2019

@darwinguz Thank you. Added "Delete" key.

@Jozx thank you for your message. I updated the directive and added a decimal limit option.

The directive is much cleaner than before :)

@albertThinkpalm
Copy link

albertThinkpalm commented Feb 3, 2020

@ahmeti How to make this directive work for negative values

@ShubhamRK96
Copy link

ShubhamRK96 commented Feb 4, 2020

@ahmeti Thank you so much. It's perfectly worked.

@aruribadraiah
Copy link

when it is putting application level it is not working in individual module it is working file , so how to application levelel ? i.e. if we put in app.module.ts file importing file and declaration of file not working..

@isoftvn
Copy link

isoftvn commented Apr 4, 2020

@ahmeti how to allow copy & paste with this?

@rob-byram
Copy link

I am using the directive with two decimal places. If the existing value has two decimals (1.23), the directive does not let me type anything else, even if the cursor is in front of the decimal point. Is there a way to get around that?

@ahmeti
Copy link
Author

ahmeti commented Apr 10, 2020

Thank you guys. I updated and please check to a new version. @isoftvn @rob-byram

@rob-byram
Copy link

@ahmeti, thank you so much!! I spent a good portion of today trying (unsuccessfully) to do a workaround that also dealt with the "selected" characters. Your update is super simple and works like a charm!

@leidy777
Copy link

@ahmeti, Hey thanks so much! It has been quite useful!

@carlosesteban55
Copy link

carlosesteban55 commented Jun 11, 2020

@ahmeti How can I change the RegExp to accept negative numbers? Equal that now, but accpeting one or none "-" at first.

Thanks so much.

@carlosesteban55
Copy link

@ahmeti How to make this directive work for negative values

+1

@ahmeti
Copy link
Author

ahmeti commented Jun 11, 2020

Now supports negative values.
But i don't want to change base directive.
I added negative prop in component. If you want you can use the directive bellow.

@albertThinkpalm @carlosesteban55

<input numeric type="text" negative="1" />
import { Directive, ElementRef, HostListener, Input } from "@angular/core";

@Directive({
  selector: "[numeric]"
})
export class NumericDirective {
  @Input("decimals") decimals: int = 0;
  @Input("negative") negative: int = 0;

  private checkAllowNegative(value: string) {
    if (this.decimals <= 0) {
      return String(value).match(new RegExp(/^-?\d+$/));
    } else {
      var regExpString =
        "^-?\\s*((\\d+(\\.\\d{0," +
        this.decimals +
        "})?)|((\\d*(\\.\\d{1," +
        this.decimals +
        "}))))\\s*$";
      return String(value).match(new RegExp(regExpString));
    }
  }

  private check(value: string) {
    if (this.decimals <= 0) {
      return String(value).match(new RegExp(/^\d+$/));
    } else {
      var regExpString =
        "^\\s*((\\d+(\\.\\d{0," +
        this.decimals +
        "})?)|((\\d*(\\.\\d{1," +
        this.decimals +
        "}))))\\s*$";
      return String(value).match(new RegExp(regExpString));
    }
  }

  private run(oldValue) {
    setTimeout(() => {
      let currentValue: string = this.el.nativeElement.value;
      let allowNegative = this.negative > 0 ? true : false;

      if (allowNegative) {
        if (
          !["", "-"].includes(currentValue) &&
          !this.checkAllowNegative(currentValue)
        ) {
          this.el.nativeElement.value = oldValue;
        }
      } else {
        if (currentValue !== "" && !this.check(currentValue)) {
          this.el.nativeElement.value = oldValue;
        }
      }
    });
  }

  constructor(private el: ElementRef) {}

  @HostListener("keydown", ["$event"])
  onKeyDown(event: KeyboardEvent) {
    this.run(this.el.nativeElement.value);
  }

  @HostListener("paste", ["$event"])
  onPaste(event: ClipboardEvent) {
    this.run(this.el.nativeElement.value);
  }
}

@carlosesteban55
Copy link

Thanks a lot @ahmeti!!

@mavazca
Copy link

mavazca commented Jun 16, 2020

Now supports negative values and separator="," default "."

<input numeric type="text" decimals="2" negative="1" separator=","/>
import { Directive, ElementRef, HostListener, Input } from "@angular/core";

@Directive({
  selector: "[numeric]"
})
export class NumericDirective {
  @Input("decimals") decimals: number = 0;
  @Input("negative") negative: number = 0;
  @Input("separator") separator: string = ".";

  private checkAllowNegative(value: string) {
    if (this.decimals <= 0) {
      return String(value).match(new RegExp(/^-?\d+$/));
    } else {
      var regExpString =
        "^-?\\s*((\\d+(\\"+ this.separator +"\\d{0," +
        this.decimals +
        "})?)|((\\d*(\\"+ this.separator +"\\d{1," +
        this.decimals +
        "}))))\\s*$";
      return String(value).match(new RegExp(regExpString));
    }
  }

  private check(value: string) {
    if (this.decimals <= 0) {
      return String(value).match(new RegExp(/^\d+$/));
    } else {
      var regExpString =
        "^\\s*((\\d+(\\"+ this.separator +"\\d{0," +
        this.decimals +
        "})?)|((\\d*(\\"+ this.separator +"\\d{1," +
        this.decimals +
        "}))))\\s*$";
      return String(value).match(new RegExp(regExpString));
    }
  }

  private run(oldValue) {
    setTimeout(() => {
      let currentValue: string = this.el.nativeElement.value;
      let allowNegative = this.negative > 0 ? true : false;

      if (allowNegative) {
        if (
          !["", "-"].includes(currentValue) &&
          !this.checkAllowNegative(currentValue)
        ) {
          this.el.nativeElement.value = oldValue;
        }
      } else {
        if (currentValue !== "" && !this.check(currentValue)) {
          this.el.nativeElement.value = oldValue;
        }
      }
    });
  }

  constructor(private el: ElementRef) {}

  @HostListener("keydown", ["$event"])
  onKeyDown(event: KeyboardEvent) {
    this.run(this.el.nativeElement.value);
  }

  @HostListener("paste", ["$event"])
  onPaste(event: ClipboardEvent) {
    this.run(this.el.nativeElement.value);
  }
}

@mavazca
Copy link

mavazca commented Jun 16, 2020

Thanks a lot @ahmeti!!

@Vinoth-deva
Copy link

thanks works as Expected, but only thing is if i use [(ngmodel)] the textbox value is not matched with ngmodel

@Asad1592
Copy link

Asad1592 commented Aug 3, 2020

@ahmeti It's working fine on Chrome but in Firefox and Internet explorer its not working. Can you please suggest?

@karan2004
Copy link

it is not working in mobile device

@shivaji33
Copy link

thanks works as Expected, but only thing is if i use [(ngmodel)] the textbox value is not matched with ngmodel

Same issue

@shivaji33
Copy link

shivaji33 commented Apr 20, 2021

thanks works as Expected, but only thing is if i use [(ngmodel)] the textbox value is not matched with ngmodel

Same issue

please set value to ngControl

private run(oldValue) {
setTimeout(() => {
let currentValue: string = this.el.nativeElement.value;
let allowNegative = this.negative > 0;
if (allowNegative) {
if (!['', '-'].includes(currentValue) && !this.checkAllowNegative(currentValue)) {
this.ngControl.control.setValue(oldValue, {emitEvent: false})
}
} else {
if (currentValue !== '' && !this.check(currentValue)) {
this.ngControl.control.setValue(oldValue, {emitEvent: false})
}
}
});
}

@rbrijesh
Copy link

rbrijesh commented Jun 2, 2021

I want to add prevent default function when user try to enter alphabetical values and other things is that when I try to add numbers at max typing speed it accept more then given decimal values.
For example Input accept 2 decimal values but when try to forcefully enter the values it accepts more then 2 decimals. Any solution will help me to prevent this thing.
Thanks in advance.

@sreekan2
Copy link

How are you removing the zeros in the front (00012)?

@lujian98
Copy link

To access ngControl, add @Optional() @Self() private ngControl: NgControl, to constructor:

 constructor(
    @Optional() @Self() private ngControl: NgControl,
    private el: ElementRef<HTMLInputElement | HTMLTextAreaElement>,
  ) { }

    if (this.ngControl) {
      this.ngControl.control.patchValue(oldValue, { emitEvent: false, onlySelf: true });
    }

@LSzelecsenyi
Copy link

LSzelecsenyi commented Dec 2, 2022

Thank you, very useful!
As @rbrijesh mentioned, if one types more than the maximum decimals, the input does not show it, but the value of the input is longer by one number. Is there a fix for this? I could not find what causes it.

@LSzelecsenyi
Copy link

Here is a fixed version. It uses NgControl.valueChanges insted of @HostListeners which fixes the extra character at the end of the input when submitting the form.

`

import { Directive, Input, OnInit } from '@angular/core';
import { NgControl } from '@angular/forms';
import { distinctUntilChanged, pairwise, Subscription } from 'rxjs';

@Directive({
    selector: '[restrictedDecimals]'
})
export class InstrumentDecimalsDirective implements OnInit {
    @Input() decimals = 0;
    @Input() negative = 0;
    @Input() separator = ',';
    valueSubscription: Subscription;

    constructor(public ngControl: NgControl) {}

    ngOnInit(): void {
        this.ngControl.valueChanges.pipe(distinctUntilChanged(), pairwise()).subscribe(([oldValue, newValue]) => {
            this.runCheck(oldValue, newValue);
        });
    }

    private runCheck(oldValue, newValue) {
        const allowNegative = this.negative > 0 ? true : false;

        if (allowNegative) {
            if (!['', '-'].includes(newValue) && !this.checkAllowNegative(newValue)) {
                this.ngControl.control.setValue(oldValue);
            }
        } else {
            if (newValue !== '' && !this.check(newValue)) {
                this.ngControl.control.setValue(oldValue);
            }
        }
    }

    private checkAllowNegative(value: string) {
        if (this.decimals <= 0) {
            return new RegExp(/^-?\d+$/).exec(String(value));
        } else {
            const regExpString =
                '^-?\\s*((\\d+(\\' + this.separator + '\\d{0,' + this.decimals + '})?)|((\\d*(\\' + this.separator + '\\d{1,' + this.decimals + '}))))\\s*$';
            return new RegExp(regExpString).exec(String(value));
        }
    }

    private check(value: string) {
        if (this.decimals <= 0) {
            return new RegExp(/^\d+$/).exec(String(value));
        } else {
            const regExpString =
                '^\\s*((\\d+(\\' + this.separator + '\\d{0,' + this.decimals + '})?)|((\\d*(\\' + this.separator + '\\d{1,' + this.decimals + '}))))\\s*$';
            return new RegExp(regExpString).exec(String(value));
        }
    }
}

`

@akshaywadatkar
Copy link

is there any new update for 13

@LSzelecsenyi
Copy link

is there any new update for 13

it works with angular v14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment