Heyjojo
Heyjojo

Reputation: 475

Best way to implement Angular cross field validation

I am trying to figure out the best way to implement cross field validation in Angular.

For example, I have a select field that makes another field mandatory.

I want to be able to:

So far, I came up with three solutions but they don't feel so convincing to me.

Here is a Stackblitz implementation that demos my investigations.

Upvotes: 4

Views: 14348

Answers (3)

Eliseo
Eliseo

Reputation: 58099

UPDATE - ANOTHER APPROACH See this SO

UPDATE - A BETTER APPROACH

Create the customValidator over the form and use the validator to use setError to the control required. Using setError, make that Angular adds ng-invalid for us, ad we needn't subscribe to value change. See:

form: FormGroup = new FormGroup(
  {
    input1: new FormControl('optional'),
    input2: new FormControl(null),
  },
  { validators: this.customValidatorForm() },
);

customValidatorForm() {
  return (form: FormGroup) => {
    const error =
      form.get('input1').value != 'optional' && !form.get('input2').value
        ? { required: true }
        : null;
    form.get('input2').setErrors(error); //<--see the setErrors
    return error;
  };
}

See stackblitz

OLD ANSWER

Just use a customValidator like:

form: FormGroup = new FormGroup({
  input1: new FormControl('optional'),
  input2: new FormControl(null, this.customValidator()),
});

customValidator() {
  return (control: any) => {
    if (!control.parent) return null;

    let mandatory = control.parent.get('input1').value;
    return mandatory != 'optional' && !control.value ? { required: true } : null;
  };
}

Another option for not ask for control.parent it's use .bind(this). This allow us have inside the validator to all the variables of our component, and of course access to this.form:

form: FormGroup = new FormGroup({
  input1: new FormControl('optional'),
  input2: new FormControl(null, this.customValidator().bind(this)), //<--bind(this)
});

customValidatorBind() {
  return (control: any) => {
    if (!this.form) return null;

    let mandatory = this.form.get('input1').value;
    return mandatory != 'optional' && !control.value ? { required: true } : null;
  };
}

Well, as we want that when change input1 input2 was checked, you need use, after create the form subscribe to valueChanges:

this.form.get('input1').valueChanges.subscribe(() => {
  this.form.get('input2').updateValueAndValidity();
});

Upvotes: 20

Sylvain Delafoy
Sylvain Delafoy

Reputation: 21

Based on the "better approach" on this comment: https://stackoverflow.com/a/57123631/8126632

I would edit it this way to prevent any other validation to be overwritten:

input2: new FormControl('', [Validators.minLength(4)]),

form.get('input2').setErrors({...error, ...(form.get('input2').errors)});

Otherwise a perfect answer.

Upvotes: 2

Srishti Khandelwal
Srishti Khandelwal

Reputation: 587

For cross field validation, you can use required validation of @rxweb/reactive-form-validation.

You just have to mention conditionalExpression in your formControl like this:

input2:['', RxwebValidators.required({conditionalExpression:'x => x.input1 == "mandatory"' })]

and set the error message in your app.component.ts like this

ngOnInit(){
    ReactiveFormConfig.set({"validationMessage":{"required":"This field is required"}});
  }

Here is your complete component code:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from "@angular/forms"
import { RxwebValidators } from '@rxweb/reactive-form-validators';

@Component({
    selector: 'app-required-conditionalExpression-validator',
    templateUrl: './required-conditional-expression.component.html'
})
export class RequiredConditionalExpressionValidatorComponent implements OnInit {
    userFormGroup: FormGroup

    constructor(
        private formBuilder: FormBuilder )
    { }

    ngOnInit() {
        this.userFormGroup = this.formBuilder.group({
            input1:[''], 
            input2:['', RxwebValidators.required({conditionalExpression:'x => x.input1 == "mandatory"' })], 
        });
    }
}

Here is your Complete HTML Code:

<div>
    <form [formGroup]="userFormGroup">

        <div>
            <label>Mandatory/Optional </label>
        <select formControlName="input1">
          <option value="optional">Optional</option>
          <option value="mandatory">Mandatory</option>
        </select>
      <br/><br/>
    </div>
    <div>
      <label>Input2</label>
      <input type="text" formControlName="input2"/><br/>
      <span>
        {{userFormGroup.controls.input2.errors?.required?.message}}
      </span>
    </div>
  </form>
</div>

Here is the Working Example

Upvotes: 3

Related Questions