Reputation: 392
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
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
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
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