I am creating a form using Angular Reactive forms, and for UX reasons I am trying to disable the form's submit when certain invalid values are inserted in an <input type="number"> field, for example negative numbers or non-integer numbers.
The problem is that when I insert a value that even if allowed by <input type="number"> would usually evaluate to NaN, like, for example, the letter e alone, the minus or plus sign without a number following it, or even a number too big to be represented by a TypeScript number, instead evaluates to null when accessing the value via control.value or control.getRawValue().
EDIT: The form also needs to be submittable when the input field is empty. This is sadly a non-negotiable requirement, and I also cannot use a default value like 0 for the field.
This is a minimum reproducible example for what I tried (it only tries to block NaN values).
app.component.ts
import { Component } from '@angular/core';
import { AbstractControl, FormBuilder, ValidatorFn } from '@angular/forms';
@Component({
selector: 'app-root',
template: `
<form [formGroup]="formGroup" (ngSubmit)="print()">
<input type="number" name="control" id="control" formControlName="control" >
<button type="submit" [disabled] ="formGroup.invalid">print</button>
</form>
`
})
export class AppComponent {
title = 'ReactiveFormNumberControl';
formGroup
constructor(private _fb: FormBuilder) {
this.formGroup = this._fb.group({
control: this._fb.control<number | null>(null, {
validators: [noNaNValidator]
})
})
}
print = () => {
console.log(this.formGroup.value)
}
}
const noNaNValidator: ValidatorFn = (
control: AbstractControl<number | null>,
) => {
const value = control.value
if (Number.isNaN(value)) {
// Value is null and not NaN, so this is never reached because Number.isNaN(null) is false,
// the control (and group) is marked valid and the submit button is not disabled
return {
invalidValue: {
value: value
},
}
}
return null
}
While looking at Angular issues related to this on GitHub, more precisely Issue #2962, I found a workaround for this, leveraging HTML5's
badInputproperty present present in some types of<input>elements,numberincluded.I've wrote a custom validator directive, similar to the one shown in this comment on Angular Issue #2962, but unlike that one the directive I've wrote has to be explicitly applied, and for now can only be applied to
<input type="number">fields (but this should be easy to change if needed, by changing the directive'sselector).bad-input-validator.directive.ts
This can probably be improved somehow, but for now it does what I need to. When I apply the
appBadInputValidatorto an<input type="number">element, and I input a value that would parse toNaN, theFormControlandFormGroupget marked as invalid and the submit button is correctly disabled.This directive works similar Angular.JS's form validation, that kept track of
badInput, but this behavior is currently not present in Angular, at least as of version 16.2.6.Big thanks to amc1804 on GitHub for posting a solution.