2

I have an Angular application that uses a reactive form. One of my input fields is of type number and I use the step directive to determine valid values as following:

steps = 0.25
myForm: FormGroup

constructor(private fb: FormBuilder) {
  myForm = this.fb.group({
      numberControl: new FormControl(null)
  });

<input type="number" step="{{steps}}" formControlName="numberControl">

The step property can be changed based on other input of the application but for simplicity I have not included that in this example. My problem here is that the form is always valid. The step works when I use the up and down arrow keys of the input field but I could enter any value manually and it would still be valid.

Stackblitz example: https://stackblitz.com/edit/angular-rhyeeb

Any help is appreciated!

3
  • You mean to say that the number input field should only be controlled only via the stepper arrows and not through keyboard. Am I right? Commented Mar 2, 2020 at 8:21
  • No I want the form to be invalid if I manually enter a value that is not a valid step. So in my example something like 0.26 Commented Mar 2, 2020 at 8:23
  • You should create then a custom validator for your input field. You can find tons of articles in google regarding that. Commented Mar 2, 2020 at 8:50

3 Answers 3

2

By default, numner input fields don't validate if the number entered manually by user is divisible by the step counter. If you want a feature like that, then you have to implement a custom validator which is pretty easy.

All you have to do is create a function to validate the number input field and then include the newly created validator in the Validators array of the FormControl object. Your validator function should either return a null value when your custom validation test passes or return a map of key-value pairs detailing the reason for failure.

Find the below code snippet demonstrating the use of custom validator to check if the current input value is a valid step or not.

export class AppComponent implements OnInit {
  steps = 0.25;
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      numberControl: new FormControl(null, [
        Validators.required,
        this.validateNumField.bind(this)
      ])
    });

    this.myForm.valueChanges.subscribe(_ => {
      if (!this.myForm.valid) {
        console.log('err msg -> ', this.myForm.get("numberControl").errors);
      }
    });
  }

  validateNumField(control: AbstractControl): { [key: string]: any } {
    let rem = control.value && Number.parseFloat(control.value) % this.steps;
    // console.log(rem);

    /**
    * If the remainder is not 0 then user has entered an invalid value
    */
    return rem ? { error: "Not a valid step" } : null;
  }
}

Now your submit button will not work if it is not a valid step. You can go ahead and show an error message on the template if you want using this.myForm.get("numberControl").errors property.

You can find an working example in stackblitz here. For more on custom validation, visit Angular docs.

Edit: Updated answer to capture change in steps value.

Sign up to request clarification or add additional context in comments.

2 Comments

I assumed that the step property would validate the input by default. Thanks for this!
I'm adding a few thoughts here: this code do not take into account min property. Ex: with a step of 2 and a min of 1, 3 is valid but 4 is not. Moreover you can easily run into rounding issue (ex: 0.3 % 0.1 will return an error)
2

You need to implement a custom validation yourself.

import { AbstractControl } from '@angular/forms';

function stepsValidator(control: AbstractControl) {
    if (!control.value || control.value % this.steps !== 0) {
        return { error: true };
    }
    return null;
}

And then in your formControl declaration

myForm = this.fb.group({
    numberControl: new FormControl(null, this.stepsValidator.bind(this))
});

You must use the bind function since custom validators has a different scope than the component their in.

Comments

1

To be more dynamic you can pass the step as a variable to the validator:

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function stepValidator(step: number): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (control.value) {
      return +control.value % step ? { step: { value: control.value } } : null;
    }
    return null;
  };
}

Then you can use it as:

formGroup = this.fb.group({
  field: new FormControl(null, this.stepValidator(this.steps))
});

And to check for validation:

<p *ngIf="formGroup.get('field')?.hasError('step')">
  Field should be a multiple of {{ this.steps }}
</p>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.