Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 4 validator to check 2 controls at the same time

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,

like image 617
Fel Avatar asked Apr 18 '18 09:04

Fel


1 Answers

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

like image 160
Tomasz Kula Avatar answered Dec 11 '22 07:12

Tomasz Kula