5

I'm creating dynamic input field it will accept all type values. I need to restrict only numbers to be enter.

template:

<tr  *ngFor="let item of rowData">
    <ng-container *ngFor="let hitem of columnDefs" >
      <td *ngIf="!hitem.dropdown; else selectDrop">
        <span *ngIf="hitem.edit;else content">
          <div *ngIf="editing">
          <input [required]="required"  [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" />
          </div>
          <div *ngIf="!editing">
            {{item[hitem.field]}}
          </div>
        </span>
      <ng-template #content>content here... {{item[hitem.field]}} </ng-template>
      </td>
      <ng-template #selectDrop>
        <td>
          <select [(ngModel)]="item[hitem.field]">
            <option *ngFor="let item of aplAry">{{item}}</option>
          </select>
        </td>
      </ng-template>
      </ng-container>
  </tr>

data:

mainHead = [{name:'', colspan:1}, {name:'Deatils', colspan:2}]
columnDefs = [
        {headerName: 'Make', field: 'make', edit:true },
        {headerName: 'Model', field: 'model', dropdown: true },
        {headerName: 'Price', field: 'price', edit:true}
];
aplAry = ['Celica','Mondeo','Boxter'];
    rowData = [
        { make: 'Toyota', model: 'Celica', price: 35000 },
        { make: 'Ford', model: 'Mondeo', price: 32000 },
        { make: 'Porsche', model: 'Boxter', price: 72000 }
];

Stackblitz example

3
  • use regex to validate Commented Oct 24, 2019 at 7:30
  • The answer marked as correct does not answer your question completely. You cannot enter a decimal and it would be very tedious to explicitly specify every single acceptable key code. Above all, it doesn't even apply to the specified input (price). I have added a concise solution for the same that does not require adding the key codes and works for decimal places.
    – nash11
    Commented Oct 24, 2019 at 20:06
  • I'm sorry, I didn't understand that last comment. What about testing 20 fields? Do you mean you don't want to specify each field in the directive? Either way I was just pointing out that the marked answer will lead to incorrect results but if it works fine for you then well and good.
    – nash11
    Commented Oct 26, 2019 at 2:06

9 Answers 9

20

You can create a custom directive for only number. Stackblitz Demo

app.component.html

<input type="text" appOnlynumber/>

onlynumber.directive.ts

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

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

  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.keyCode < 48 || e.keyCode > 57)) &&
      (e.keyCode < 96 || e.keyCode > 105)
    ) {
      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);
  }


}
3
  • Based on the condition how to add 'appOnlynumber' directive respective input field?
    – Rijo
    Commented Oct 24, 2019 at 17:47
  • And also remember to declare the directive in the app.module.ts inside declarations! Good job!
    – Joan Gil
    Commented Jun 5, 2020 at 7:20
  • the document.execCommand is showing deprecated, can you please suggest any alternative?
    – Hope
    Commented May 24, 2022 at 9:22
2

Here's a simpler approach using a directive.

export class NumbersOnlyDirective {
    @Input('field') field;

    constructor(private ngControl: NgControl) { }

    @HostListener('input', ['$event']) onInput(event): void {
        if (this.field === 'price') {
            const value = event.target.value;
            this.ngControl.control.setValue(parseFloat(value) || 0);
            if (value.slice(-1) === '.' && !value.slice(0, -1).includes('.')) {
                event.target.value = value;
            }
        }
    }
}

This directive will only allow decimal numbers to be entered. parseFloat removes the alphabets and other special characters. I have used || 0 as a fallback in case the field is emptied but if you don't want anything to display, simply use || '' instead. The if condition ensures that only one decimal point can be entered unlike when you use type="number" (type="number" will also change the ngModel to a string). The condition is placed after we update the control value so that if the last entered value is a ., the ngModel value will not include the . while the view will contain it.

Then use this directive in your template like below and pass the field value so that the this logic will only apply to the price field.

<input [required]="required" numbersOnly [field]="hitem.field" [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" />

Here is a working example on StackBlitz.

1
  • 1
    This is very clean option, thanks. I did have an issue with trying to capture zeroes immediately after the decimal point (eg, 31.001), so I swapped out the enitre if (value.slice... statement that handles the decimal point with a decimal regex value matching: event.target.value = (value.match(/\d+\.?\d*/g) || [null])[0]; Commented Sep 29, 2020 at 19:38
2

To do it dynamically,

include type in columnDefs objects:

Working Demo

 columnDefs = [
    { headerName: "Make", field: "make", edit: true, type: "text" },
    { headerName: "Model", field: "model", dropdown: true, type: "text" },
    { headerName: "Price", field: "price", edit: true, type: "number" }
  ];

Template:

<input [required]="required" [type]="hitem.type" [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" />
6
  • Have you tested this? I can enter alphabets and digits
    – Rijo
    Commented Oct 24, 2019 at 6:23
  • Ya.. i saw that, fixing it, meanwhile check demo 1 Commented Oct 24, 2019 at 6:30
  • @Rijo Since in 2nd demo,type text or number was based om model value, if we selected the whole text and deleted, the model value becomes blank, so it took text, deleted that part from the answer Commented Oct 24, 2019 at 6:34
  • I thought its browser dependency, I tried for other browser its not working :)
    – Rijo
    Commented Oct 24, 2019 at 6:34
  • Let us continue this discussion in chat.
    – Rijo
    Commented Oct 24, 2019 at 6:40
2

You can achieve by using directive.

@Directive({
  selector: "input[numbersOnly]"
})
export class NumberDirective {
  constructor(private _el: ElementRef) {}
  @HostListener("keydown", ["$event"])
  onKeyDown(e: KeyboardEvent) {
    if (
      // Allow: Delete, Backspace, Tab, Escape, Enter
      [46, 8, 9, 27, 13].indexOf(e.keyCode) !== -1 ||
      (e.keyCode === 65 && e.ctrlKey === true) || // Allow: Ctrl+A
      (e.keyCode === 67 && e.ctrlKey === true) || // Allow: Ctrl+C
      (e.keyCode === 86 && e.ctrlKey === true) || // Allow: Ctrl+V
      (e.keyCode === 88 && e.ctrlKey === true) || // Allow: Ctrl+X
      (e.keyCode === 65 && e.metaKey === true) || // Cmd+A (Mac)
      (e.keyCode === 67 && e.metaKey === true) || // Cmd+C (Mac)
      (e.keyCode === 86 && e.metaKey === true) || // Cmd+V (Mac)
      (e.keyCode === 88 && e.metaKey === true) || // Cmd+X (Mac)
      (e.keyCode >= 35 && e.keyCode <= 39) // Home, End, Left, Right
    ) {
      return; // let it happen, don't do anything
    }
    // 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();
    }
  }
}

Show the directive in html,

 <input [required]="required" [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" numbersOnly/>

Declare the directive in AppModule

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent,NumberDirective ],
  bootstrap:    [ AppComponent ]
})

Working Stackblitz

Update 1

If you like to allow numbers only in price field only:

<input [required]="required" [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" [numbersOnly]="hitem.headerName!='Make'"/>

and in directive,

export class NumberDirective {
  @Input() numbersOnly:boolean;
  constructor(private _el: ElementRef) {}
  @HostListener("keydown", ["$event"])
  onKeyDown(e: KeyboardEvent) {
    if(!this.numbersOnly)
      return;
    ....// rest of codes
}
1

Add type="number" in your input element

<input [required]="required"  [name]="item[hitem.field]" [(ngModel)]="item[hitem.field]" type="number" />

If you want to change input type dynamically then [type]="type" & in your .ts file set type="text | number | email | tel, etc"

6
  • How to limit maximum entry 5
    – Rijo
    Commented Oct 24, 2019 at 6:12
  • set max="5" in input field
    – kunwar97
    Commented Oct 24, 2019 at 6:13
  • I tried max="5" but i can enter more digit even 'e' also
    – Rijo
    Commented Oct 24, 2019 at 6:15
  • try this oninput="if(value > 5) value=5" in input tag
    – kunwar97
    Commented Oct 24, 2019 at 6:29
  • If you want to do it the angular way, then create a directive which sets input value to 5 if it exceeds the max value or you can use form control
    – kunwar97
    Commented Oct 24, 2019 at 6:30
0
@Directive({
    selector: '[cdeCurrencyPipe]',
})
export class CDECurrencyPipe {
    private navigationKeys = ['Backspace','Delete','Tab','Escape','Enter','Home','End','ArrowLeft','ArrowRight','Clear','Copy','Paste'];
    @Input() OnlyNumber: boolean;
   
    constructor(private control : NgControl, 
            private elementRef: ElementRef,
            private currencyPipe: CurrencyPipe) { }

    @HostListener('blur', ['$event.target.value'])
    onBlur(raw) {
        if(raw && raw.length > 0){
            var strVal = raw.replace(/[$,]/g,"");
            this.elementRef.nativeElement.value = this.currencyPipe.transform(strVal);
        }
    }

    @HostListener('focus', ['$event.target.value'])
    onChange(raw) {
        if(raw && raw.length > 0){
            var strVal = raw.replace(/[$,]/g,"");
            this.control.valueAccessor.writeValue(strVal)
        }
    }

    @HostListener('keydown', ['$event'])
    onKeyDown(e: KeyboardEvent) {
        if (
            // Allow: Delete, Backspace, Tab, Escape, Enter, etc
            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.keyCode < 48 || e.keyCode > 57)) &&
            (e.keyCode < 96 || e.keyCode > 105)
        ) {
            e.preventDefault();
        }
    }
}
0

It can be done as follow:

in component.html

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

in component.ts

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

}
0

Add validation like this

sufficient_observation_count: new FormControl( 0,[Validators.pattern("^(0|[1-9][0-9]{0,100})$"),Validators.required])

The pattern will check that first digit shoud be whether 0 or from 1 to 9,and then subsequent 100 digits can be from 0 to 9,so it checks for decimals,negative numbers,exponentials etc.

0

Instead of using keycode to detect the input (whether char or number) its better to use regular expression to match the input type. Below is a directive doing that. The advantage of using regex is that your code becomes flexible. For example if you need to limit number of digits in your decimal number to 7 max & decimal places to 2 then you can simply change the regex to /^[0-9]{1,7}(.[0-9]{0,2}){0,1}$/g

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

    private decRegex: RegExp = new RegExp(/^[0-9]+(\.[0-9]*){0,1}$/g); 
      private specialKeys: Array<string> = ['Backspace', 'Tab', 'End', 'Home', 'ArrowLeft', 'ArrowRight', 'Delete', 'Enter', 'Control', 'Cut', 'Copy', 'Paste'];
    
      targetElement: any;
    
      constructor(element: ElementRef) {
        this.targetElement = element.nativeElement;
    
      }
    
      @HostListener('paste', ['$event'])
      onPaste(event: ClipboardEvent) {
    
        const pastedValue = event.clipboardData.getData('text/plain');
        console.log(pastedValue);
    
        if (pastedValue && !String(pastedValue).match(this.decRegex)) {
            event.preventDefault(); //prevent the paste from happening
        }
    
      }
    
    
      @HostListener('keydown', ['$event'])
      onKeyDown(event: KeyboardEvent) {
    
        if (this.specialKeys.indexOf(event.key) !== -1) {
          return; //if any of the special keys like backspace, delete, arrow keys etc. are pressed, then return
        }
    
        const inputTextBoxValue: string = this.targetElement.value.concat(event.key); //concatenate the previous value with the new typed value
    
        console.log(`event.key: ${event.key}, inputValue: ${inputTextBoxValue}`);
    
        if (inputTextBoxValue && !String(inputTextBoxValue).match(this.decRegex)) {
          event.preventDefault(); //if its not a number then prevent the keypress
        }
    
        return;
    
      }
    
    }

Usage:

<input allowOnlyNumbers type = "text" id = "someId" formControlName = "someName" />

Not the answer you're looking for? Browse other questions tagged or ask your own question.