angulartypescriptformsangular-reactive-formsangular-custom-validators

Angular Form Group is not clearing conditional validator errors


I have a dropdown, and based on the selected option, I display specific form controls. For the dropdown option value2, I display an error message: "Please provide either the Body or both Username and Password," indicating that one of these fields is required in order to save the form.The issue is that when I select option value1 or value3 from the dropdown, the error message (h6) still appears on the form, and the atLeastOne validation error persists. I am unable to reset or clear this error, even after changing the dropdown value.

Template Code:

    <form [formGroup]="form">
        <h2 style="display: inline-block;">{{action}} {{operationName}}</h2>
        <h6 style="color: red; display: inline-block;margin: 0;margin-left: 0.3rem;"
            *ngIf="isSaveDisabled()">
            (Required Field is/are missing)
        </h6>
        <h6 style="color: red; display: inline-block;margin: 0;margin-left: 0.3rem;"
            *ngIf="form.getError('atLeastOne') != null">
            (Please provide either the Auth Request Body or both Username and Password)
        </h6>
        <ng-container [ngSwitch]="column['type']">
            <!-- Text Input -->
            <!-- Dropdown Input -->
            <select *ngSwitchCase="'dropdown'" id="{{column['label']}}"
                    formControlName="{{column['label']}}"
                    (change)="handleDropdownChange(column['label'])">
                <option value="">ALL</option>
                <option *ngFor="let item of column['options']" [value]="item">
                    {{item}}
                </option>
            </select>
        </ng-container>
    </form>

ts file:-

    ngOnInit() {
    this.columns = [
        {
            label: 'Type',
            value: '',
            type: 'dropdown',
            required: true,
            options: ['value1', 'value2', 'value3'],
        },
        {
            label: 'Body',
            value: '',
            type: 'inputTextArea',
            required: true,
        },
        {
            label: 'Username',
            value: '',
            type: 'inputTextField',
            required: true,
        },
        {
            label: 'Password',
            value: '',
            type: 'inputTextField',
            required: true,
        },
    ];
    this.columns?.forEach((column) => {
      const control = this.fb.control(
        column.value,
        column.required ? Validators.required : null
      );
      this.form.addControl(column.label, control);
    });

    if (value === 'value2') {
      console.log('this is called');
      this.form.get('Username')!.clearValidators();
      this.form.get('Password')!.clearValidators();
      this.form.get('Body')!.clearValidators();
      this.form.setValidators(this.conditionalValidator());
      this.form.updateValueAndValidity();
      this.cdr.detectChanges();
    }
    console.log('Form after adding controls: ', this.form.value);
  }
        }

    handleDropdownChange(value: string): void {
        console.log('Handling Option2 change:', value);
        console.log('Before:', JSON.stringify(this.form.errors, null, 2));
        console.log('1 ' + this.form.getError('required'));
        // Clear errors on each form control
        Object.keys(this.form.controls).forEach((key) => {
            const control = this.form.get(key);
            console.log('Control error:', control?.errors);
            if (control) {
                control.setErrors(null);
                control.updateValueAndValidity();
            }
        });
        console.log('2 ' + this.form.getError('required'));
        // On dropdown change we have to clear the errors
        console.log('1 ' + this.form.getError('atLeastOne'));
        this.form.setErrors({ atLeastOne: 'good' });
        this.form.updateValueAndValidity();
        console.log('2 ' + this.form.getError('atLeastOne'));
    
        this.form.get('atLeastOne')?.setErrors({
            atLeastOne: 'Atasdsd.',
        });
    

        this.form.updateValueAndValidity();
        this.cdr.detectChanges();
        this.clearRequiredValidators();
        // this.form.updateValueAndValidity();
        console.log('Form errors after:', JSON.stringify(this.form.errors, null, 2));
        this.populateForm(value);
    }


conditionalValidator(): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const body = control.get('Body');
      const username = control.get('Username');
      const password = control.get('Password');
      if (body && authRequestBody.value.length > 0) {
        return null;
      }
      if (
        username &&
        password &&
        username.value.length > 0 &&
        password.value.length > 0
      ) {
        return null; 
      }
      return { atLeastOne: 'At least one field has to be provided.' }; 
    };
  }

Console prints:-

Before: {
  "atLeastOne": "At least one field has to be provided."
}
consumer-details.component.ts:418 1 undefined
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: {required: true}
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:422 Control error: null
consumer-details.component.ts:432 2 undefined
consumer-details.component.ts:434 1 At least one field has to be provided.
consumer-details.component.ts:437 2 At least one field has to be provided.
consumer-details.component.ts:446 Form errors afte: {
  "atLeastOne": "At least one field has to be provided."
}

h6 message is getting displayed even after changing dropdown values. I am using custom validator function


Solution

  • The problem is that you never remove your conditionalValidator. Meaning that regardless of the dropdown selection, that validator continues to run.

    One solution would be to check whether the received value is equal to value2 or not. If equal, set the validator otherwise remove it (this.form.setValidators(null))

    For example:

    handleDropdownChange(value: string): void {
      // Remove any existing form errors. (It might not be necessary in your case)
      this.form.setErrors(null);
    
      // If the user chose 'value2', apply your conditional validator
      if (value === 'value2') {
        this.form.setValidators(this.conditionalValidator());
      } else {
        // otherwise remove it
        this.form.setValidators(null);
      }
      
      // Re-validate
      this.form.updateValueAndValidity();
    
      // additional logic …
    
      this.cdr.detectChanges();
      this.populateForm(value);
    }