2

The conditional validator has probably been asked a lot. But what I could never find is when this validator is to be invoked.

Suppose I have a form with only two fields

this.form = formBuilder.group({
  emailRequired: [false],
  emailRecipients: ['']
});

I now want the field emailRecipients to have a required validator ONLY when the emailRequired field is set to true. I came up with this

const emailRecipientsControl = fb.control('', {
    validators: [
        (x) =>
        {
            // When emailRequired is set to true, the email control is required.
            if (x.parent?.get('emailRequired').value)
            {
                return { required: true }
            };
            return null;
        }
    ]
});
this.form = this.fb.group({
    emailRequired: [false],
    emailRecipients: emailRecipientsControl
});

This actually somewhat works, but the problem is that my custom validator is only invoked when the emailRecipients value has been changed, whereas I need it to be invoked when BOTH emailRecipients or emailRequired have been changed.

So to summarize: how do I define WHEN to invoke (custom) control validation?

Note: my real form is a lot larger then this example and I'd rather not subscribe to the entire form.

2 Answers 2

2

As you say, Angular does not "automatically validate" an input, if you don't use setValue or change the input. So the simpler solution is to force this validation when you change the checkbox.

<input type="check" formControlName="emailRequired" (input)="form.controls.emailRecipients.updateValueAndValidity()">

Update Based on recommendation from @MoxxiManagarm, It would be better if we can define our formGroup and without needing to take into account when, we need subscribe to valueChanges or use (input).

We can define a "validator" like

export function checkControl(controlName:string)
{
  return (x:AbstractControl) =>
  {
    const control=x?.parent?.get(controlName);
    if (control)
      control.updateValueAndValidity()
    return null;
  }
}

This allows us to write,

  form = new FormGroup({
    emailRequired: new FormControl(false,checkControl('emailRecipients')),
    emailRecipients: new FormControl('',requireIf('emailRequired'))
  });

Then we simply,

<form [formGroup]="form">
    <input type="checkbox" formControlName="emailRequired">
    <input formControlName="emailRecipients">
    {{form.get('emailRecipients')?.errors|json}}
</form>

NOTE: the validator "requiredIf" like

export function requireIf(controlName:string)
{
  return (x:AbstractControl) =>
  {
      if (x?.parent?.get(controlName)?.value && !x.value)
      {
          return { required: true }
      };
      return null;
  }
}

See that, when we change the "checkbox", the formControl form.controls.emailRecipiens are updateAndValidity.

Well, this "validators" makes the typical repeat password validators problem more "robust".

export function equalsTo(controlName:string)
{
  return (x:AbstractControl) =>
  {
      const control=x?.parent?.get(controlName);
      if (control && control.value && control.value!=x.value)
      {
          return { notMatch: true }
      };
      return null;
  }
}

The form

  form2=new FormGroup({
    password:new FormControl('',checkControl('repeatPassword')),
    repeatPassword:new FormControl('',equalsTo('password'))

  })

A stackblitz

Update 2 Improving the checkControl to allow more that one control

export function checkControls(...controlNames:string[]) {
  return (x: AbstractControl) => {
    controlNames.forEach((name:string)=>{
      const control = x?.parent?.get(name);
      if (control) control.updateValueAndValidity();
  
    })
    return null;
  };
}

This allows us to use something like,

emailRequired: new FormControl(false,
        checkControls('emailRecipient1','emailRecipient2')),
2
  • The biggest issue I see here comes from a maintenance point of view. Imagine at some point emailRequired will not be set by the user input only, but also programmatically. You will always need to remember, that at every point that changes emailRequired you also need to update the validity. That is a huge risk for mistakes. With listing to the value itself you handle all those cases at once and you don't need some custom validators. Commented Oct 27, 2023 at 13:43
  • @MoxxiManagarm, but, why nor only updateValueAndValidity when change? Really I don't like Remove/Add validators. note: it's only an very, very personal opinion.
    – Eliseo
    Commented Oct 27, 2023 at 16:57
2

You need to subscribe to valuechanges and add/remove the validator.

this.form = formBuilder.group({
  emailRequired: [false],
  emailRecipients: ['']
});

this.form.controls.emailRequired.valueChanges.pipe(
  /*unsubscription*/,
).subscribe(isRequired => {
  if (isRequired) {
    this.form.controls.emailRecipients.addValidators(Validators.required);
  } else {
    this.form.controls.emailRecipients.removeValidators(Validators.required);
  }
  this.form.controls.emailRecipients.updateValueAndValidity();
});
0

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.