Angular: Cross Field Validation Not Working with setError()

I have a cross field validation method that should fail if one of the following evaluates to true:

  • country == 'United States' and stateSelect is empty
  • country != 'United States' and stateText is empty

country and stateSelect are both <select> items while sateText is an <input type="text">. When I load the page country = 'United States' and stateSelect = {name: 'Alabama', abbr: 'AL'}, and everything evalues properly.

If I change country to another value in the select, then change it back to United States, it evaluates properly, but my button stays disabled.

<form [formGroup]="purchase" class="forum-purchase__grid">
  <!-- inputs and selects -->
  <button [disabled]="purchase.invalid">Next</button>
</form>

The component looks like this:

    public constructor() {
        this.purchase = new FormGroup({
            stateSelect: new FormControl(this.selectedState),
            stateText: new FormControl(''),
            country: new FormControl(this.selectedCountry, [Validators.required]),
        }, { validators: [this.stateRegionValidator] });
    }

    private stateRegionValidator(formGroup: FormGroup): ValidationErrors | null {
        const stateSelect = formGroup.get('stateSelect');
        const stateText = formGroup.get('stateText');
        const country = formGroup.get('country');

        // Validate the fields
        if (country.value === 'United States' && !stateSelect.value) {
            stateSelect.setErrors({ 'badState': true });
            return { 'badState': true };
        } else if (country.value !== 'United States' && !stateText.value.trim().length) {
            stateText.setErrors({ 'badRegion': true });
            return { 'badRegion': true };
        }

        // The validation was successful.
        return null;
    }

I do notice that this works if I remove these two lines, however, I don't get error styles on the inputs then:

stateSelect.setErrors({ 'badState': true });
stateText.setErrors({ 'badRegion': true });

Working example

1

3 Answers

So your button is still disabled, because you did not remove the previously set errors from your different form controls.

When you add following above your return null; it will work:

stateSelect.setErros(null);
stateText.setErrors(null);
2

The issue is what you called out - you need to remove the setErrors calls. Those are side effects of your group validator which validates the form as a whole. When those set errors on the individual fields, nothing can unset them.

You should probably write custom validators for each field that use the logic in your group validator function, while not calling setErrors and just returning null or a value. Or keep the group validator, and show a single error for when the group has an error.

In some cases, I was needed to do something like this. listen on value change:

    <select [(ngModel)]="selectedCountry" (ngModelChange)="test($event)"  formControlName="country">

and in TS:

    countryUpdate($event: string) {
      if ($event == 'United States') {
        this.purchase.controls.stateText.clearValidators();
      } else {
        this.purchase.controls.stateText.setValidators([this.stateRegionValidator]);
      }
      this.purchase.updateValueAndValidity();
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Alexander Ross

Alexander Ross

Gaming, Esports & Interactive Media Writer

Alexander Ross has covered the video game industry for a decade, writing deep dives on game design, esports tournaments, VR developments, and gaming culture.

Share this article
Twitter Facebook Pinterest