Input type number "only numeric value" validation

FormsAngularValidation

Forms Problem Overview


How can I validate an input of type="number" to only be valid if the value is numeric or null using only Reactive Forms (no directives)?
Only numbers [0-9] and . are allowed, no "e" or any other characters.


What I've tried so far:

Template:
<form [formGroup]="form" novalidate>
    <input type="number" formControlName="number" id="number">
</form>
Component:
export class App {
  form: FormGroup = new FormGroup({});
  
  constructor(
    private fb: FormBuilder,
  ) {
    this.form = fb.group({
      number: ['', [CustomValidator.numeric]]
    })
  }
}
CustomValidator:
export class CustomValidator{
  // Number only validation
  static numeric(control: AbstractControl) {
    let val = control.value;
    
    if (val === null || val === '') return null;
    
    if (!val.toString().match(/^[0-9]+(\.?[0-9]+)?$/)) return { 'invalidNumber': true };
    
    return null;
  }
}

Plunker

The problem is when a user enters something that is not a number ("123e" or "abc") the FormControl's value becomes null, keep in mind I don't want the field to be required so if the field really is empty null value should be valid.
> Cross browser support is also important (Chrome's number input fields do not allow the user to input letters - except "e", but FireFox and Safari do).

Forms Solutions


Solution 1 - Forms

In the HTML file, you can add ngIf for your pattern like this,

<div class="form-control-feedback" *ngIf="Mobile.errors && (Mobile.dirty || Mobile.touched)">
        <p *ngIf="Mobile.errors.pattern" class="text-danger">Number Only</p>
      </div>

In .ts file you can add the Validators pattern -"^[0-9]*$"

this.Mobile = new FormControl('', [
  Validators.required,
  Validators.pattern("^[0-9]*$"),
  Validators.minLength(8),
]);

Solution 2 - Forms

Using directive it becomes easy and can be used throughout the application

HTML

<input type="text" placeholder="Enter value" numbersOnly>

As .keyCode() and .which() are deprecated, codes are checked using .key() Referred from

Directive:

@Directive({
   selector: "[numbersOnly]"
})

export class NumbersOnlyDirective {
  @Input() numbersOnly:boolean;

  navigationKeys: Array<string> = ['Backspace']; //Add keys as per requirement
  
  constructor(private _el: ElementRef) { }

  @HostListener('keydown', ['$event']) onKeyDown(e: KeyboardEvent) {
    
    if (
      // Allow: Delete, Backspace, Tab, Escape, Enter, etc
      this.navigationKeys.indexOf(e.key) > -1 || 
      (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) || // Cmd+A (Mac)
      (e.key === 'c' && e.metaKey === true) || // Cmd+C (Mac)
      (e.key === 'v' && e.metaKey === true) || // Cmd+V (Mac)
      (e.key === 'x' && e.metaKey === true) // Cmd+X (Mac)
    ) {
        return;  // let it happen, don't do anything
    }
    // Ensure that it is a number and stop the keypress
    if (e.key === ' ' || isNaN(Number(e.key))) {
      e.preventDefault();
    }
  }
}

Solution 3 - Forms

Try to put a minimum input and allow only numbers from 0 to 9. This worked for me in Angular Cli

<input type="number" oninput="this.value=this.value.replace(/[^\d]/,'')"  min=0>

Solution 4 - Forms

The easiest way would be to use a library like this one and specifically you want noStrings to be true

    export class CustomValidator{   // Number only validation   
      static numeric(control: AbstractControl) {
        let val = control.value;
    
        const hasError = validate({val: val}, {val: {numericality: {noStrings: true}}});
    
        if (hasError) return null;
    
        return val;   
      } 
    }

Solution 5 - Forms

I had a similar problem, too: I wanted numbers and null on an input field that is not required. Worked through a number of different variations. I finally settled on this one, which seems to do the trick. You place a Directive, ntvFormValidity, on any form control that has native invalidity and that doesn't swizzle that invalid state into ng-invalid.

Sample use: <input type="number" formControlName="num" placeholder="0" ntvFormValidity>

Directive definition:

import { Directive, Host, Self, ElementRef, AfterViewInit } from '@angular/core';
import { FormControlName, FormControl, Validators } from '@angular/forms';

@Directive({
  selector: '[ntvFormValidity]'
})
export class NtvFormControlValidityDirective implements AfterViewInit {

  constructor(@Host() private cn: FormControlName, @Host() private el: ElementRef) { }

  /* 
  - Angular doesn't fire "change" events for invalid <input type="number">
  - We have to check the DOM object for browser native invalid state
  - Add custom validator that checks native invalidity
  */
  ngAfterViewInit() {
    var control: FormControl = this.cn.control;

    // Bridge native invalid to ng-invalid via Validators
    const ntvValidator = () => !this.el.nativeElement.validity.valid ? { error: "invalid" } : null;
    const v_fn = control.validator;

    control.setValidators(v_fn ? Validators.compose([v_fn, ntvValidator]) : ntvValidator);
    setTimeout(()=>control.updateValueAndValidity(), 0);
  }
}

The challenge was to get the ElementRef from the FormControl so that I could examine it. I know there's @ViewChild, but I didn't want to have to annotate each numeric input field with an ID and pass it to something else. So, I built a Directive which can ask for the ElementRef.

On Safari, for the HTML example above, Angular marks the form control invalid on inputs like "abc".

I think if I were to do this over, I'd probably build my own CVA for numeric input fields as that would provide even more control and make for a simple html.

Something like this:

<my-input-number formControlName="num" placeholder="0">

PS: If there's a better way to grab the FormControl for the directive, I'm guessing with Dependency Injection and providers on the declaration, please let me know so I can update my Directive (and this answer).

Solution 6 - Forms

Simplest and most effective way to do number validation is (it will restrict space and special character also)

if you dont want length restriction you can remove maxlength property

HTML

<input type="text" maxlength="3" (keypress)="validateNo($event)"/>

TS

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

Solution 7 - Forms

You need to use regular expressions in your custom validator. For example, here's the code that allows only 9 digits in the input fields:

function ssnValidator(control: FormControl): {[key: string]: any} {
  const value: string = control.value || '';
  const valid = value.match(/^\d{9}$/);
  return valid ? null : {ssn: true};
}

Take a look at a sample app here:

https://github.com/Farata/angular2typescript/tree/master/Angular4/form-samples/src/app/reactive-validator

Solution 8 - Forms

Sometimes it is just easier to try something simple like this.

validateNumber(control: FormControl): { [s: string]: boolean } {

  //revised to reflect null as an acceptable value 
  if (control.value === null) return null;

  // check to see if the control value is no a number
  if (isNaN(control.value)) {
    return { 'NaN': true };
  }

  return null; 
}

Hope this helps.

updated as per comment, You need to to call the validator like this

number: new FormControl('',[this.validateNumber.bind(this)])

The bind(this) is necessary if you are putting the validator in the component which is how I do it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMihailoView Question on Stackoverflow
Solution 1 - FormsIshara MadawaView Answer on Stackoverflow
Solution 2 - FormssabinView Answer on Stackoverflow
Solution 3 - FormsLautaroOsinagaView Answer on Stackoverflow
Solution 4 - FormsluanpedView Answer on Stackoverflow
Solution 5 - FormsAndrew PhilipsView Answer on Stackoverflow
Solution 6 - FormsShabeer MView Answer on Stackoverflow
Solution 7 - FormsYakov FainView Answer on Stackoverflow
Solution 8 - FormsKenView Answer on Stackoverflow