angulartypescriptangular-reactive-formsangular2-form-validation

Angular ValidatorFn returns, but does not block Form


I have build a Custom Validator for my Form, which returns expected ValidationErrors object.

ValidateDirectory(clientId: string): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
            const ret: ValidationErrors = { isValidDirectory: true };
            if (control.value == null || control.value === '') {
                return ret;
            }
            this.service.getList(clientId, control.value).subscribe((res) => {
                if (res.technicalRetCode !== 0) {
                    console.log(ret);
                    return ret;
                }
                return null;
            });
            return null;
        };
    }

I proved this with console.log line, this actually logs my object.

The Validator is attached besides a Validators.required in ngOnInit

ngOnInit() {
  ...
  this.fourthFormGroup = this._formBuilder.group({
      dateipfad: ['', {validator: Validators.compose([Validators.required, this.directoryValidator.ValidateDirectory(this.clientID)]), updateOn: 'blur'}]
    });
  ... 
}

Problem is: the required Validator works as intended, but my custom Validator just logs.

I have tried quite a lot, like using validators with array instead of Validators.compose, tweaked my Validator method to set control.setErrors(ret) some other things, nothing worked.

I feel like there is quite a simple solution, but I just can't find it...

EDIT 1: I created a stackblitz https://stackblitz.com/edit/angular-ysyb9i?file=src%2Fapp%2Fapp.component.html

Explanation: the input takes any strings, on deselect the validation kicks in. if Input is 'false' it should set input to invalid by returning const ret: ValidationErrors = { isValidDirectory: true };, any other string should be valid and it returns null.

Both results are logged on console, so you can check it. If input is invalid it should show a notification div below (not quite sure, just added it for this stackblitz to show)


Solution

  • You should be using async validator if the code inside your validator is async.

    this.fourthFormGroup = this._formBuilder.group({
      dateipfad: ['', {
        validators: [Validators.required],
        asyncValidators: [this.directoryValidator.ValidateDirectory(this.clientID)],
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                        put it here
        updateOn: 'blur'
      }]
    });
    

    The validator should return AsyncValidatorFn interface where you should subscribe to observable but rather map to the value you want. Angular will subscribe automatically to your observable.

    ValidateDirectory(clientId: string): AsyncValidatorFn {
      return (control: AbstractControl): 
                 Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
        const ret: ValidationErrors = { isValidDirectory: true };
    
        if (control.value == null || control.value === '') {
          return of(ret);
        }
    
        return this.service.getList(clientId, control.value).pipe(map((res: any) => {
          if (res.technicalRetCode !== 0) {
            return ret;
          }
    
          return null;
        }));
      };
    }
    

    Forked Stackblitz

    Also please do note that async validators are fired only when all sync validators return null. So you won't see the error from the start because you have synchronous required validator in place.