1

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

6
  • You mean before submitting the form the on submit get called? Commented Jan 2, 2020 at 13:08
  • no no, the value of "this.form.invalid" fails and the success message is displayed before the custom form validation is resolved. Commented Jan 2, 2020 at 13:14
  • I need to make the form to be invalid until the custom validations are complete. I don't think setting the value manually is a good way. Commented Jan 2, 2020 at 13:16
  • Is there any way to make the reactive form to wait for custom validation to resolve. that execute submit function only if the form validations are complete Commented Jan 2, 2020 at 13:17
  • Have you tried FormGroup pending property? Commented Jan 2, 2020 at 13:29

3 Answers 3

1

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'
        }
    );
}
Sign up to request clarification or add additional context in comments.

3 Comments

I tried your answer, I gave a valid email for which the error " no user found" is displayed. but the form submission code is also running.
the form didn't wait for the validation to complete
I added my angular repo for you
1

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');
        }
      }

9 Comments

This didn't do any change. The form behaves as before
I tried your answer, and the error keeps happening. with which I found that the success message is displayed before the form validation is triggered!
I added my angular repo for you
I don't get any error but the reactive form executes the submission code despite validation fails.
you mean invalid false?
|
0

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 )) 

6 Comments

I tried your answer. it didn't work. also, it fails to add errors like " userExist: true ". The error is not even showing in the form.
What do you mean 'not showing'? Have you try to debug with ng.probe($0).componentInstance in console?
checkUser(email: any) { const url = 'localhost:3000/api/auth/user'; const headers: HttpHeaders = new HttpHeaders({ 'Content-Type': 'application/json' }); const body: any = JSON.parse(JSON.stringify({ email })); const options = { headers }; return this.http.post(url, body, options) .pipe( map(data => new AuthResponse(data)) ); }
should I pipe again?
It's okey. Are you sure that you dont have exception on Json.parse and stringify?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.