Angular 4 validator to check 2 controls at the same time

Fel picture Fel · Apr 18, 2018 · Viewed 8.2k times · Source

I have a reactive form with 2 controls (port_start and port_end) that have the following requirements:

  • Both must have a value
  • Their values must be between 0 and 65535
  • port_start value must be less than port_end value

This is what I tried so far:

[...]
this.formModel.addControl('port_start', 
  new FormControl(object.port_start ? object.port_start : 0, 
  [Validators.required, Validators.min(0), Validators.max(65535), this.minMaxValidator('port_start', 'port_end').bind(this)]));

this.formModel.addControl('port_end', 
  new FormControl(object.ort_end ? object.port_end : 0, 
  [Validators.required, Validators.min(0), Validators.max(65535), this.minMaxValidator('port_start', 'port_end').bind(this)]));
[...]

This is the custom validator function:

minMaxValidator = function(startControl : string, endControl : string): ValidatorFn {
  return (control: FormControl): {[key: string]: any} => {
    let valid = true;
    let valStart = 0;
    let valEnd = 0;

    if(this.formModel.controls[startControl] && this.formModel.controls[endControl]) {
      valStart = <number>this.formModel.controls[startControl].value;

      valEnd = <number>this.formModel.controls[endControl].value;
    }

    valid = valEnd >= valStart;

    return valid ? null : { minmax : true };
  };
}

This works fine except for this problem:

  • Let's say I type '2' in the 'port_start' field. Angular marks it as non valid because it's more than the value of 'port_end' (which is 0 by default). If I type '5' in the 'port_end' field, the app still shows 'port_start' as invalid, although now it's correct.

I understand that the problem is that I need to re-check the associated field each time I change the other one's value, but I don't know how to do it.

Any ideas? Thanks,

Answer

Tomasz Kula picture Tomasz Kula · Apr 18, 2018

The min, max and required validators can be kept as is. If you want to validate one control based on the value of another, you need to lift the validation to the parent control.

import { Component } from '@angular/core';
import { ValidatorFn, FormBuilder, FormGroup, Validators } from '@angular/forms';

const portStartEnd: ValidatorFn = (fg: FormGroup) => {
   const start = fg.get('portStart').value;
   const end = fg.get('portEnd').value;

   return start && end && start < end ? null : { startEnd: true };
}

@Component({
  selector: 'my-app',
  template: `
   <input [formControl]="form.get('portStart')" type="number" >
   <input [formControl]="form.get('portEnd')" type="number" >

   {{ form.valid }}
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      portStart: [null, [Validators.required, Validators.min(0), Validators.max(65535)]],
      portEnd: [null, [Validators.required, Validators.min(0), Validators.max(65535)]]
    }, { validator: portStartEnd } );
  }
}

Live demo