Vivek Nuna
Vivek Nuna

Reputation: 1

How to validate between two fields in Angular?

I have two fields. One is min price and another one is max price. I want to have validation that min price should always be less than max price and max price should be greater than min price. In simple positive case, it works fine.

But it's not working for some cases.

Let's say user enter 12 in min and 100 in max. then If user types 123332 or some big number than 100.it does give the error message.

<div class="col-md-3" >
<label for="minimumPriceTextbox" class="control-label">{{l("MinPrice")}}</label>
<input type="text"
       [minValue]="0" [maxValue]="test.maxPrice"
       maxlength="17" class="form-control c" id="minimumPriceTextbox"
       name="minimumPriceTextbox"
       placeholder="Minimum Price"
       [value]="test.minPrice"
       [(ngModel)]="test.minPrice"
       #minimumPriceTextbox="ngModel">
<div *ngIf="minimumPriceTextbox?.invalid && (minimumPriceTextbox?.dirty || minimumPriceTextbox?.touched)" class="has-error help-block help-block-error font-red-mint">
    <span> {{l("MinPriceShouldBeLessThanMaxPrice.")}} </span>
</div>
{{test.minPrice}}
</div>
<div class="col-md-3">
<label for="maximumPriceTextbox" class="control-label">{{l("MaxPrice")}}</label>
<input name="maximumPriceTextbox"
       type="text"
       [minValue]="test.minPrice"
       maxlength="17" class="form-control c" id="maximumPriceTextbox"
       placeholder="Maximum Price"
       [value]="test.maxPrice"
       [(ngModel)]="test.maxPrice"
       #maximumPriceTextbox="ngModel">
<div *ngIf="maximumPriceTextbox?.invalid && (maximumPriceTextbox?.dirty || maximumPriceTextbox?.touched)" class="has-error help-block help-block-error font-red-mint">
    <span> {{l("MaxPriceShouldBeGreaterThanMinPrice.")}} </span>
</div>
{{test.maxPrice}}
</div>

minValue directive code:

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


@Directive({
    selector: '[minValue]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => MinValueValidator), multi: true }
    ]
})
export class MinValueValidator implements Validator {

    @Input('minValue') minValue: number;

    validate(control: AbstractControl): { [key: number]: any } {
        let givenvalue = control.value;
        let validationResult = null;

        let minValue = this.minValue;
        if (minValue && givenvalue < minValue) {
            validationResult = validationResult || {};
            validationResult.minValue = true;
        }

        return validationResult;
    }
}

maxValue directive code:

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


@Directive({
    selector: '[maxValue]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => MaxValueValidator), multi: true }
    ]
})
export class MaxValueValidator implements Validator {

    @Input('maxValue') maxValue: number;

    validate(control: AbstractControl): { [key: number]: any } {
        let givenvalue = control.value;
        let validationResult = null;

        let maxValue = this.maxValue;
        if (maxValue && givenvalue > maxValue) {
            validationResult = validationResult || {};
            validationResult.maxValue = true;
        }

        return validationResult;
    }
}

Upvotes: 4

Views: 2871

Answers (2)

kemsky
kemsky

Reputation: 15279

There is no easy way to do cross validation, however it is possible to create your own custom validator directive, the idea is to pass form component names to validator and subscribe for changes of these components within validator:

export interface IValidatorFactory
{
    (input:any, key:string):SyncValidatorResult;
}

@Directive({
    selector : 'input[sync]',
    exportAs : 'sync',
    providers: [{provide: NG_VALIDATORS, useExisting: forwardRef(() => GenericValidator), multi: true}]
})
export class GenericValidator implements AsyncValidator, OnInit
{
    @Input('sync')
    public factory:IValidatorFactory;

    @Input('deps')
    public deps:string[];

    private onChange:() => void;

    private readonly form:FormGroup;

    constructor(form:NgForm)
    {
        this.form = form.form;
    }

    public ngOnInit():any
    {
        //wait for ngmodel created
        setTimeout(() =>
        {
            if(Array.isArray(this.deps) && this.deps.length > 0)
            {
                const values:any = Object.create(null);

                for(const controlName of this.deps)
                {
                    const control = this.form.get(controlName);
                    if(control == null)
                    {
                        console.error('Dependant control was not found', controlName);
                    }
                    else
                    {
                        control.valueChanges.subscribe(changes =>
                        {
                            if(values[controlName] !== changes)
                            {
                                values[controlName] = changes;
                                this.onChange();
                            }
                        });
                    }
                }
            }
        }, 0);
    }

    public registerOnValidatorChange(fn:() => void):void
    {
        this.onChange = fn;
    }

    public validate(c:AbstractControl):AsyncValidatorResult;
    public validate(c:AbstractControl):SyncValidatorResult
    {
        return this.validatorFn(c);
    }

    private validatorFn(formControl:AbstractControl):SyncValidatorResult
    {
        if(formControl.value != null && this.factory != null)
        {
            return this.factory(formControl.value, 'syncValidator');
        }
        return null;
    }
}

    public onValidate(c:AbstractControl):ValidatorResult
    {
        return this.validatorFn(c);
    }

    private validatorFn(formControl:AbstractControl):SyncValidatorResult
    {
        if(formControl.value != null && this.factory != null)
        {
            return this.factory(formControl.value, 'syncValidator');
        }
        return null;
    }
}

Template:

    <input name="field1" type="text" [(ngModel)]="item.field1" [sync]="validateFields" [deps]="['field2']"></div>
    <input name="field2" type="text" [(ngModel)]="item.field2" [sync]="validateFields" [deps]="['field1']"></div>

Component:

public validateFields:IValidatorFactory = (value:string, key:string):SyncValidatorResult =>
{
    let result:SyncValidatorResult = null;

    if(...)
    {
        result = Object.create(null);
        result[key] = {
            valid  : false,
            message: 'invalid values'
        };
    }
    return result;
};

Upvotes: 1

user4676340
user4676340

Reputation:

Instead of a directive, can't you use a simple function ? Something like (sorry, I'll reduce the code to minimum)

<input type="number" (input)="checkValidity()" [(ngModel)]="maxValue">

In your TS :

checkValidity() {
  if (this.maxValue < this.minValue) { /* Set your error here */ }
  else if (this.minValue > this.maxValue) { /* Set your error here */ }
  else { /* Set your other errors, or your validation here */ }
}

Upvotes: 0

Related Questions