I\'m trying to implement a custom validator to check if the password and password confirm are equal. The problem is that the validator is getting undefined password and conf
I would do the same as Shailesh Ladumor but adding the following line before returning in the validation function:
c.get('confirm_password').setErrors({'noMatch': true});
So that the validation function looks like this:
passwordConfirming(c: AbstractControl): { invalid: boolean } {
if (c.get('password').value !== c.get('confirm_password').value) {
c.get('confirm_password').setErrors({'noMatch': true});
return {invalid: true};
}
}
This will not only set the hole userForm
as an invalid form group, but it will also set confirm_password
as an invalid form control.
With this you can later call the following function in your template:
public getPasswordConfirmationErrorMessage() {
if (this.userForm.get('confirm_password').hasError('required')) {
return 'You must retype your password';
} else if (this.userForm.get('confirm_password').hasError('noMatch')) {
return 'Passwords do not match';
} else {
return '';
}
}
When you need to validate on more than one field, and you wish to declare validator at form creation time, FormGroup validator must be used. The main issue with form validator is that it attaches error to form and not to validating control, which leeds to some inconsistents in template. Here is reusable form validator wich attaches error to both form and control
// in validators.ts file
export function controlsEqual(
controlName: string,
equalToName: string,
errorKey: string = controlName // here you can customize validation error key
) {
return (form: FormGroup) => {
const control = form.get(controlName);
if (control.value !== form.get(equalToName).value) {
control.setErrors({ [errorKey]: true });
return {
[errorKey]: true
}
} else {
control.setErrors(null);
return null
}
}
}
// then you can use it like
ngOnInit() {
this.vmForm = this.fb.group({
username: ['', [Validators.required, Validators.email]],
password: ['', [
Validators.required,
Validators.pattern('[\\w\\d]+'),
Validators.minLength(8)]],
confirm: [''], // no need for any validators here
}, {
// here we attach our form validator
validators: controlsEqual('confirm', 'password')
});
}
The issue is that you are mixing the reactive forms module with the input approach. This is causing you to get undefined
when passing the values to the validator.
You don't need to bind to the ng-model
when using the reactive forms. Instead, you should access the value of the fields from the Instance of FormGroup
.
I do something like this in an app to validate the passwords match.
public Credentials: FormGroup;
ngOnInit() {
this.Credentials = new FormGroup({});
this.Credentials.addControl('Password', new FormControl('', [Validators.required]));
this.Credentials.addControl('Confirmation', new FormControl(
'', [Validators.compose(
[Validators.required, this.validateAreEqual.bind(this)]
)]
));
}
private validateAreEqual(fieldControl: FormControl) {
return fieldControl.value === this.Credentials.get("Password").value ? null : {
NotEqual: true
};
}
Note that the validator expects a FormControl
field as a parameter and it compares the value of the field to that of the value of the Password
field of the Credentials
FormGroup
.
In the HTML
make sure to remove the ng-model
.
<input type="password" class="form-control" formControlName="confirmedPassword" title="Please re-enter your password" >
<!-- AND -->
<input type="password" class="form-control" formControlName="password" title="Please enter your password">
Hope this helps!
Please update FormGroup code like below in Angular5
this.signUpForm = new FormGroup({
'username': new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(15)]),
'email': new FormControl(null, [Validators.required, Validators.email, Validators.minLength(5)]),
'password': new FormControl(null, [Validators.required]),
'confirmedPassword': new FormControl(null, [Validators.required])
}, this.pwdMatchValidator);
Add pwdMatchValidator
function in your component
pwdMatchValidator(frm: FormGroup) {
return frm.get('password').value === frm.get('confirmedPassword').value
? null : {'mismatch': true};
}
Add validation message in your template
<span *ngIf="confirmedPassword.errors || signUpForm .errors?.mismatch">
Password doesn't match
</span>
Please find the below angular material working component.
Component Templete Code password.component.html
<form class="cahnge-pwd-form" (ngSubmit)="onSubmit()" name="passwordForm" [formGroup]="passwordForm" #formDir="ngForm">
<div fxLayout='column'>
<mat-form-field>
<input matInput name="password" placeholder="Password" [type]="hide ? 'text' : 'password'" formControlName="password" required>
<mat-icon matSuffix (click)="hide = !hide">{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
<mat-error *ngIf="password.invalid && (password.dirty || password.touched || isSubmit)">
<span *ngIf="password.errors.required">
Please enter a Password.
</span>
<span *ngIf="password.errors.maxlength">
Please enter a Email no more than 16 characters.
</span>
<span *ngIf="password.errors.minlength">
Please enter a password at least 6 characters.
</span>
</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput name="password" placeholder="Confirm Password" [type]="confirm_hide ? 'text' : 'password'" formControlName="confirm_password"
required>
<mat-icon matSuffix (click)="confirm_hide = !confirm_hide">{{confirm_hide ? 'visibility_off' : 'visibility'}}</mat-icon>
<mat-error *ngIf="(confirm_password.invalid && (confirm_password.dirty || confirm_password.touched || isSubmit) || passwordForm.errors?.mismatch)">
<span *ngIf="confirm_password.errors || passwordForm.errors?.mismatch">
Password doesn't match
</span>
</mat-error>
</mat-form-field>
<div fxLayout='row' fxLayoutGap="10px">
<button type="submit" mat-raised-button color="primary">Submit</button>
<button type="button" (click)="formDir.resetForm(passwordForm)" mat-raised-button color="warn">Cancel</button>
</div>
</div>
</form>
Component Code : password.component.ts
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { FormControl, FormGroup, Validators, FormBuilder } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { PasswordService } from './password.service';
import { PasswordValidation } from './confirm';
@Component({
selector: 'app-password',
templateUrl: './password.component.html',
styleUrls: ['./password.component.css']
})
export class PasswordComponent implements OnInit {
passwordForm: FormGroup;
isSubmit: boolean;
constructor(private router: Router, private passwordService: PasswordService, private toastrService: ToastrService, private route: ActivatedRoute) { }
ngOnInit() {
this.passwordForm = new FormGroup({
'password': new FormControl('', [
Validators.required,
Validators.minLength(6),
Validators.maxLength(16),
]),
'confirm_password': new FormControl('', [
Validators.required,
Validators.minLength(6),
Validators.maxLength(16),
]),
}, this.pwdMatchValidator);
}
pwdMatchValidator(frm: FormGroup) {
return frm.get('password').value === frm.get('confirm_password').value
? null : {'mismatch': true};
}
get password() { return this.passwordForm.get('password'); }
get confirm_password() { return this.passwordForm.get('confirm_password'); }
onSubmit(formvalue):boolean {
this.isSubmit = true;
if (this.passwordForm.invalid) {
return false;
} else {
this.passwordService.updatePassword(this.passwordForm.value)
.subscribe((res) => {
if (res.status == 'success') {
this.toastrService.success(res.msg);
this.router.navigate(['/change-password']);
}
})
return true;
}
}
}
When you're creating the validator, you're passing in the values of password
and confirmedPassword
, but changes in those values will not be reflected in the validator.
The two options I see are:
FormGroup
, and look up the values from the two controls you want to compare; orthis
already, use this.password
and this.confirmedPassword
in your validator.import {AbstractControl, FormBuilder, FormGroup, Validators} from
set your password input into the group and no need to use "ngModel".
<div class="form-group row" formGroupName="passwords">
<div class="form-group">
<label for="password" class="control-label">Contraseña:</label>
<input type="password" class="form-control" formControlName="password" title="Please enter your password">
<p class="help-block" *ngIf="signUpForm.get('password').hasError('required') && signUpForm.get('password').touched">Debe ingresar una contraseña</p>
</div>
<div class="form-group">
<label for="confirmedPassword" class="control-label">Confirmar Contraseña:</label>
<input type="password" class="form-control" formControlName="confirmedPassword" title="Please re-enter your password">
<p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('required') && signUpForm.get('confirmedPassword').touched">Password must be required</p>
<p class="help-block" *ngIf="signUpForm.get('confirmedPassword').hasError('passwordMismatch') && signUpForm.get('confirmedPassword').touched">password does not match</p>
</div>
buildForm(): void {
this.userForm = this.formBuilder.group({
passwords: this.formBuilder.group({
password: ['', [Validators.required]],
confirm_password: ['', [Validators.required]],
}, {validator: this.passwordConfirming}),
});
}
add this custom function for validate password and confirm password
passwordConfirming(c: AbstractControl): { invalid: boolean } {
if (c.get('password').value !== c.get('confirm_password').value) {
return {invalid: true};
}
}
Display error when password does not match
<div style='color:#ff7355' *ngIf="userForm.get(['passwords','password']).value != userForm.get(['passwords','confirm_password']).value && userForm.get(['passwords','confirm_password']).value != null">
Password does not match</div>