[validation] Min / Max Validator in Angular 2 Final

According to thoughtgram.io, the currently supported validators are:

  • required
  • minlength
  • maxlength
  • pattern

So, considering the following code (plunkr here):

@Component({
  selector: 'my-app',
  template: `
  
  <form #formRef="ngForm">
    <input type="number" [(ngModel)]="firstValue" name="firstValue" min="0" required/>
    <input type="text" [(ngModel)]="secondValue" maxlength="5" name="secondValue" required/>
    <button type="submit"> Submit </button> 
  </form>
  
  FORM: {{formRef.form | json }}
`
})
export class AppComponent { 
  firstValue = -22;
  secondValue = "eyy macarena!"; 
}

While minlength is supported, min="0" is ignored by angular validation:

enter image description here

enter image description here

So, to make the form result in an error when firstValue ngModel < 0, do I need to build a custom validator?

This question is related to validation angular typescript angular2-forms

The answer is


You can implement your own validation (template driven) easily, by creating a directive that implements the Validator interface.

import { Directive, Input, forwardRef } from '@angular/core'
import { NG_VALIDATORS, Validator, AbstractControl, Validators } from '@angular/forms'

@Directive({
  selector: '[min]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MinDirective, multi: true }]
})
export class MinDirective implements Validator {

  @Input() min: number;

  validate(control: AbstractControl): { [key: string]: any } {

    return Validators.min(this.min)(control)

    // or you can write your own validation e.g.
    // return control.value < this.min ? { min:{ invalid: true, actual: control.value }} : null



  }

}

Find the custom validator for min number validation. The selector name of our directive is customMin.

custom-min-validator.directive.ts

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, FormControl } from '@angular/forms';

@Directive({
  selector: '[customMin][formControlName],[customMin][formControl],[customMin][ngModel]',
  providers: [{provide: NG_VALIDATORS, useExisting: CustomMinDirective, multi: true}]
})
export class CustomMinDirective implements Validator {
  @Input()
  customMin: number;

  validate(c: FormControl): {[key: string]: any} {
      let v = c.value;
      return ( v < this.customMin)? {"customMin": true} : null;
  }
} 

Find the custom validator for max number validation. The selector name of our directive is customMax.

custom-max-validator.directive.ts

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, FormControl } from '@angular/forms';

@Directive({
  selector: '[customMax][formControlName],[customMax][formControl],[customMax][ngModel]',
  providers: [{provide: NG_VALIDATORS, useExisting: CustomMaxDirective, multi: true}]
})
export class CustomMaxDirective implements Validator {
  @Input()
  customMax: number;

  validate(c: FormControl): {[key: string]: any} {
      let v = c.value;
      return ( v > this.customMax)? {"customMax": true} : null;
  }
} 

We can use customMax with formControlName, formControl and ngModel attributes.

Using Custom Min and Max Validator in Template-driven Form

We will use our custom min and max validator in template-driven form. For min number validation we have customMin attribute and for max number validation we have customMax attribute. Now find the code snippet for validation.

<input name="num1" [ngModel]="user.num1" customMin="15" #numberOne="ngModel">
<input name="num2" [ngModel]="user.num2" customMax="50"  #numberTwo="ngModel"> 

We can show validation error messages as following.

<div *ngIf="numberOne.errors?.customMin"> 
     Minimum required number is 15.
</div>  

<div *ngIf="numberTwo.errors?.customMax"> 
     Maximum number can be 50.
</div> 

To assign min and max number we can also use property biding. Suppose we have following component properties.

minNum = 15;
maxNum = 50; 

Now use property binding for customMin and customMax as following.

<input name="num1" [ngModel]="user.num1" [customMin]="minNum" #numberOne="ngModel">
<input name="num2" [ngModel]="user.num2" [customMax]="maxNum"  #numberTwo="ngModel"> 

I've found this as a solution. Create a custom validator as follow

minMax(control: FormControl) {
      return parseInt(control.value) > 0 && parseInt(control.value) <=5 ? null : {
        minMax: true
      }
  }

and under constructor include the below code

this.customForm= _builder.group({
                  'number': [null, Validators.compose([Validators.required, this.minMax])],
                });

where customForm is a FormGroup and _builder is a FormBuilder.


As far as I know, is it implemented now, check https://github.com/angular/angular/blob/master/packages/forms/src/validators.ts

This is the part that implements what you are looking for:

 export class Validators {
  /**
   * Validator that requires controls to have a value greater than a number.
   */
  static min(min: number): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (isEmptyInputValue(control.value) || isEmptyInputValue(min)) {
        return null;  // don't validate empty values to allow optional controls
      }
      const value = parseFloat(control.value);
      // Controls with NaN values after parsing should be treated as not having a
      // minimum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-min
      return !isNaN(value) && value < min ? {'min': {'min': min, 'actual': control.value}} : null;
    };
  }

  /**
   * Validator that requires controls to have a value less than a number.
   */
  static max(max: number): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if (isEmptyInputValue(control.value) || isEmptyInputValue(max)) {
        return null;  // don't validate empty values to allow optional controls
      }
      const value = parseFloat(control.value);
      // Controls with NaN values after parsing should be treated as not having a
      // maximum, per the HTML forms spec: https://www.w3.org/TR/html5/forms.html#attr-input-max
      return !isNaN(value) && value > max ? {'max': {'max': max, 'actual': control.value}} : null;
    };
  }

Angualr itself provide a min and max number validation functionality.

Example - we have a field like age range then see the use of validation.

  age_range : ['',  Validators.min(1), Validators.max(18)]]

the age always be between 1 to 18.


Angular 6 supports min & max validators: https://angular.io/api/forms/Validators

You can use those for static & dynamic values.

Static:

<input min="0" max="5">

Dynamic:

<input [min]="someMinValue" [max]="someMaxValue">

In latest Angular versions, min and max are already added. Here is the link: https://angular.io/api/forms/Validators#max

This is how I used Max validator in my project:

<mat-form-field class="globalInput">
          <input (change)="CalculateAmount()" matInput placeholder="Quantity" name="productQuantity" type="number" [formControl]="quantityFormControl">
        </mat-form-field>
        <mat-error *ngIf="quantityFormControl.hasError('max')">
          Only <strong>{{productQuantity}}</strong> available!
        </mat-error>

Initialize the form control and add the validator in the component:

  quantityFormControl = new FormControl('', Validators.max(15));

You can also set validator dynamically on an event like this:

  quantityFormControl = new FormControl();

OnProductSelected(){
    this.quantityFormControl.setValidators(Validators.max(this.someVariable));
  }

Hope it helps.


Angular has min and max validators but only for Reactive Forms. As it says in the docs: "The validator exists only as a function and not as a directive."

To be able to use these validators in template-driven forms you need to create custom directives. In my implementation i use @HostBinding to also apply the HTML min/max-attributes. My selectors are also quite specific to prevent validation running on custom form controls that implements ControlValueAccessor with a min or max input (e.g. MatDatePickerInput)

min-validator:

import { Directive, HostBinding, Input } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, Validators } from '@angular/forms';

@Directive({
  selector: 'input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MinValidatorDirective, multi: true }]
})
export class MinValidatorDirective implements Validator {
  @HostBinding('attr.min') @Input() min: number;

  constructor() { }

  validate(control: AbstractControl): ValidationErrors | null {
    const validator = Validators.min(this.min);
    return validator(control);
  }
}

max-validator:

import { Directive, HostBinding, Input } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, Validators } from '@angular/forms';

@Directive({
  selector: 'input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MaxValidatorDirective, multi: true }]
})
export class MaxValidatorDirective implements Validator {
  @HostBinding('attr.max') @Input() max: number;

  constructor() { }

  validate(control: AbstractControl): ValidationErrors | null {
    const validator = Validators.max(this.max);
    return validator(control);
  }
}


USE

Validators.min(5)

It can be used while creating a formGroup variable along with other validators, as in

dueAmount: ['', [Validators.required, Validators.pattern(/^[+]?([0-9]+(?:[\.][0-9]*)?|\.[0-9]+)$/), Validators.min(5)]]

Not sure if it is in Angular 2, but is available in Angular 5


In your code you are using min and not minlength. Please also notice that this will not validate if a number is > 0 but its length.


Angular now supports min/max validators by default.

Angular provides the following validators by default. Adding the list here so that new comers can easily get to know what are the current supported default validators and google it further as per their interest.

  • min
  • max
  • required
  • requiredTrue
  • email
  • minLength
  • maxLength
  • pattern
  • nullValidator
  • compose
  • composeAsync

you will get the complete list Angular validators

How to use min/max validator: From the documentation of Angular -

static min(min: number): ValidatorFn 
static max(max: number): ValidatorFn 

min()/max() is a static function that accepts a number parameter and returns A validator function that returns an error map with the min/max property if the validation check fails, otherwise null.

use min validator in formControl, (for further info, click here)

const control = new FormControl(9, Validators.min(10));

use max validator in formControl, (for further info, click here)

const control = new FormControl(11, Validators.max(10));

sometimes we need to add validator dynamically. setValidators() is the saviour. you can use it like the following -

const control = new FormControl(10);
control.setValidators([Validators.min(9), Validators.max(11)]);

I found a library implementing a lot of custom validators - ng2-validation - that can be used with template-driven forms (attribute directives). Example:

<input type="number" [(ngModel)]="someNumber" name="someNumber" #field="ngModel" [range]="[10, 20]"/>
<p *ngIf="someNumber.errors?.range">Must be in range</p>

  1. Switch to use reactive forms instead of template forms (they are just better), otherwise step 5 will be slightly different.
  2. Create a service NumberValidatorsService and add validator functions:

    import { Injectable } from '@angular/core';
    import { FormControl,  ValidatorFn } from '@angular/forms';
    
    @Injectable()
    export class NumberValidatorsService {
    
     constructor() { }
    
      static max(max: number): ValidatorFn {
    return (control: FormControl): { [key: string]: boolean } | null => {
    
      let val: number = control.value;
    
      if (control.pristine || control.pristine) {
        return null;
      }
      if (val <= max) {
        return null;
      }
      return { 'max': true };
      }
    }
    
     static min(min: number): ValidatorFn {
    return (control: FormControl): { [key: string]: boolean } | null => {
    
      let val: number = control.value;
    
      if (control.pristine || control.pristine) {
        return null;
      }
      if (val >= min) {
        return null;
      }
      return { 'min': true };
      }
    }
    
    }
    
  3. Import service into module.

  4. Add includes statement in component where it is to be used:

        import { NumberValidatorsService } from "app/common/number-validators.service";
    
  5. Add validators to form builder:

        this.myForm = this.fb.group({
          numberInputName: [0, [Validators.required, NumberValidatorsService.max(100), NumberValidatorsService.min(0)]],
        });
    
  6. In the template, you can display the errors as follows:

     <span *ngIf="myForm.get('numberInputName').errors.max">
             numberInputName cannot be more than 100. 
      </span>
    

I was looking for the same thing now, used this to solve it.

My code:

this.formBuilder.group({
  'feild': [value,  [Validators.required, Validators.min(1)]]
});

I've added a max validation to amd's great answer.

import { Directive, Input, forwardRef } from '@angular/core'
import { NG_VALIDATORS, Validator, AbstractControl, Validators } from '@angular/forms'

/*
 * This is a wrapper for [min] and [max], used to work with template driven forms
 */

@Directive({
  selector: '[min]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MinNumberValidator, multi: true }]
})
export class MinNumberValidator implements Validator {

  @Input() min: number;

  validate(control: AbstractControl): { [key: string]: any } {
    return Validators.min(this.min)(control)
  }
}

@Directive({
  selector: '[max]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MaxNumberValidator, multi: true }]
})
export class MaxNumberValidator implements Validator {

  @Input() max: number;

  validate(control: AbstractControl): { [key: string]: any } {
    return Validators.max(this.max)(control)
  }
}

Apparently, Angular had the max/min directives for template driven forms at some point but had to remove them in v4.2.0. You can read about the regression that caused the removal here: https://github.com/angular/angular/issues/17491

For now the only working solution that I know of is to use custom directive as @amd suggested. Here's how to use it with Bootstrap 4.

min-validator.directive.ts

import { Directive, Input } from '@angular/core'
import { NG_VALIDATORS, Validator, AbstractControl, Validators } from '@angular/forms'

@Directive({
  selector: '[min]',
  providers: [{ provide: NG_VALIDATORS, useExisting: MinDirective, multi: true }]
})
export class MinDirective implements Validator {

  @Input() min: number;

  validate(control: AbstractControl): { [key: string]: any } {    
    return Validators.min(this.min)(control)    
  }
}

And in your template:

<input type="number" [min]="minAge" #age="ngModel" [(ngModel)]="person.age" class="form-control" [ngClass]="{'is-invalid':age.invalid}">
<div *ngIf="age.invalid && (age.dirty || age.touched)" class="invalid-feedback">You need to be older than {{minAge}} to participate</div>

Hope this helps!


This question has already been answered. I'd like to extend the answer from @amd. Sometimes you might need a default value.

For example, to validate against a specific value, I'd like to provide it as follows-

<input integerMinValue="20" >

But the minimum value of a 32 bit signed integer is -2147483648. To validate against this value, I don't like to provide it. I'd like to write as follows-

<input integerMinValue >

To achieve this you can write your directive as follows

import {Directive, Input} from '@angular/core';
import {AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, Validators} from '@angular/forms';

@Directive({
    selector: '[integerMinValue]',
    providers: [{provide: NG_VALIDATORS, useExisting: IntegerMinValidatorDirective, multi: true}]
})
export class IntegerMinValidatorDirective implements Validator {

    private minValue = -2147483648;

    @Input('integerMinValue') set min(value: number) {
        if (value) {
            this.minValue = +value;
        }
    }

    validate(control: AbstractControl): ValidationErrors | null {
        return Validators.min(this.minValue)(control);
    }

}

In my template driven form (Angular 6) I have the following workaround:

 <div class='col-sm-2 form-group'>
            <label for='amount'>Amount</label>
            <input type='number' 
                   id='amount' 
                   name='amount' 
                   required 
                   [ngModel] = 1
                   [pattern] = "'^[1-9][0-9]*$'"
                   class='form-control' 
                   #amountInput='ngModel'/>
            <span class='text-danger' *ngIf="amountInput.touched && amountInput.invalid">
              <p *ngIf="amountInput.errors?.required">This field is <b>required</b>!</p>
              <p *ngIf="amountInput.errors?.pattern">This minimum amount is <b>1</b>!</p>
            </span>
        </div>

Alot of the above examples make use of directives and custom classes which do scale better in more complex forms, but if your looking for a simple numeric min, utilize pattern as a directive and impose a regex restriction on positive numbers only.


Examples related to validation

Rails 2.3.4 Persisting Model on Validation Failure Input type number "only numeric value" validation How can I manually set an Angular form field as invalid? Laravel Password & Password_Confirmation Validation Reactjs - Form input validation Get all validation errors from Angular 2 FormGroup Min / Max Validator in Angular 2 Final How to validate white spaces/empty spaces? [Angular 2] How to Validate on Max File Size in Laravel? WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for jquery

Examples related to angular

error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class error TS1086: An accessor cannot be declared in an ambient context in Angular 9 TS1086: An accessor cannot be declared in ambient context @angular/material/index.d.ts' is not a module Why powershell does not run Angular commands? error: This is probably not a problem with npm. There is likely additional logging output above Angular @ViewChild() error: Expected 2 arguments, but got 1 Schema validation failed with the following errors: Data path ".builders['app-shell']" should have required property 'class' Access blocked by CORS policy: Response to preflight request doesn't pass access control check origin 'http://localhost:4200' has been blocked by CORS policy in Angular7

Examples related to typescript

TS1086: An accessor cannot be declared in ambient context Element implicitly has an 'any' type because expression of type 'string' can't be used to index Angular @ViewChild() error: Expected 2 arguments, but got 1 Typescript: No index signature with a parameter of type 'string' was found on type '{ "A": string; } Understanding esModuleInterop in tsconfig file How can I solve the error 'TS2532: Object is possibly 'undefined'? Typescript: Type 'string | undefined' is not assignable to type 'string' Typescript: Type X is missing the following properties from type Y length, pop, push, concat, and 26 more. [2740] Can't perform a React state update on an unmounted component TypeScript and React - children type?

Examples related to angular2-forms

How can I manually set an Angular form field as invalid? Angular 2 Cannot find control with unspecified name attribute on formArrays Remove all items from a FormArray in Angular Angular ReactiveForms: Producing an array of checkbox values? How to make readonly all inputs in some div in Angular2? tslint / codelyzer / ng lint error: "for (... in ...) statements must be filtered with an if statement" Min / Max Validator in Angular 2 Final TypeScript-'s Angular Framework Error - "There is no directive with exportAs set to ngForm" Can't bind to 'formGroup' since it isn't a known property of 'form' ngModel cannot be used to register form controls with a parent formGroup directive