Sivaramakrishnan
Sivaramakrishnan

Reputation: 392

Angular Reactive forms don't wait for custom async validators

I have a login component in my angular-CLI app. I have fields email and password. I created two custom validations => one for checking if the user exists, and other for checking if the password matches the user. I checked the working of built-in validators like a required field and valid email. They work fine. The problem is that my custom validators show errors only after the submission is called. The reactive form is not waiting for the custom async validators to resolve.

This is my code:

import {Component, OnInit} from '@angular/core';
import {FormGroup, FormBuilder, Validators} from '@angular/forms';
import {AuthService} from '../auth.service';
import {noUser, pwdMisMatch} from '../validators';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {

  form: FormGroup;
  submitted = false;
  returnUrl: string;

  constructor(private formBuilder: FormBuilder, public authService: AuthService) {
    this.form = this.formBuilder.group({
        email: ['', [Validators.required, Validators.email]],
        password: ['', Validators.required],
      },
      {
        validators: [noUser(authService, 'email'), pwdMisMatch(authService, 'email', 'password')]
        , updateOn: 'blur'
      }
    );
  }

  ngOnInit() {
    this.returnUrl = '/dashboard';
    this.authService.logout();
  }

  get f() {
    return this.form.controls;
  }

  onSubmit() {
    this.submitted = true;

    // stop here if form is invalid
    if (this.form.invalid) {
      return;
    } else {
      alert('Success');
    }
  }

}

This is my custom validators file:

import {FormGroup} from '@angular/forms';
import {AuthResponse, AuthService} from './auth.service';

export function MustMatch(controlName: string, matchingControlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];
    const matchingControl = formGroup.controls[matchingControlName];

    if (matchingControl.errors && !matchingControl.errors.mustMatch) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    if (control.value !== matchingControl.value) {
      matchingControl.setErrors({ mustMatch: true });
    } else {
      matchingControl.setErrors(null);
    }
  };
}

export function userExist(authservice: AuthService, controlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];

    if (control.errors && !control.errors.CheckUser) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    authservice.checkUser(control.value).subscribe((res: AuthResponse) => {
      if (res.ok) {
        control.setErrors({ userExist: true });
      } else {
        control.setErrors(null);
      }
    });
  };
}

export function noUser(authService: AuthService, controlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];

    if (control.errors && !control.errors.noUser) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    authService.checkUser(control.value).subscribe((res: AuthResponse) => {
      if (!res.ok) {
        control.setErrors({ noUser: true });
      } else {
        control.setErrors(null);
      }
    });
  };
}

export function pwdMisMatch(authService: AuthService, controlName: string, secureControlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];
    const secureControl = formGroup.controls[secureControlName];

    if (control.errors || secureControl.errors && !secureControl.errors.pwdMisMatch) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    authService.verifyPassword(control.value, secureControl.value).subscribe((res: AuthResponse) => {
      if (!res.ok) {
        secureControl.setErrors({ pwdMisMatch: true });
      } else {
        control.setErrors(null);
      }
    });
  };
}

I tried this answer and the problem is not solved. Please help.

Update: my angular repo

Upvotes: 1

Views: 2529

Answers (3)

Chellappan வ
Chellappan வ

Reputation: 27293

Angular customValidator function should return error or null in order to work.

FormGroup has pending status you can use that to check whether the async validator has completed or not.

Try this:

export function noUser(authService: AuthService, controlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];

    if (control.errors && !control.errors.noUser) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    authService.checkUser(control.value).subscribe((res: AuthResponse) => {
      if (!res.ok) {
        return { noUser: true };
      } else {
        return null;
      }
    });
  };
}

export function pwdMisMatch(authService: AuthService, controlName: string, secureControlName: string) {
  return (formGroup: FormGroup) => {
    const control = formGroup.controls[controlName];
    const secureControl = formGroup.controls[secureControlName];

    if (control.errors || secureControl.errors && !secureControl.errors.pwdMisMatch) {
      // return if another validator has already found an error on the matchingControl
      return;
    }

    // set error on matchingControl if validation fails
    authService.verifyPassword(control.value, secureControl.value).subscribe((res: AuthResponse) => {
      if (!res.ok) {
        rerurn { pwdMisMatch: true };
      } else {
        return null;
      }
    });
  };
}

 onSubmit() {
        this.submitted = true;

        // stop here if form is invalid
        if (this.form.pending && this.form.invalid) {
          return;
        } else {
          alert('Success');
        }
      }

Upvotes: 1

Dmitry Sobolevsky
Dmitry Sobolevsky

Reputation: 1191

The issue that your async validations functions are not return Promise or Observable

From angular documentation:

Async validators: functions that take a control instance and return a Promise or Observable that later emits a set of validation errors or null. You can pass these in as the third argument when you instantiate a FormControl

Dont use Subscribe here:

authservice.checkUser(control.value).subscribe()

use pipe() transformation instead:

authservice.checkUser(control.value). pipe(map(res => res && res.ok ? ({ userExist: true }) : null )) 

Upvotes: 0

Virgi
Virgi

Reputation: 11

Maybe you should identify as an async validators, like that :

constructor(private formBuilder: FormBuilder, public authService: AuthService) {
    this.form = this.formBuilder.group(
        {
            email: ['', [Validators.required, Validators.email]],
            password: ['', Validators.required],
        },
        {
            asyncValidators: [noUser(authService, 'email'), pwdMisMatch(authService, 'email', 'password')]
            , updateOn: 'blur'
        }
    );
}

Upvotes: 1

Related Questions