I am working on a reactive registration form in Angular 16.
I have made a custom validation to check if the values in the form fields password
and confirm_password
match:
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
export const matchpassword : ValidatorFn = (control: AbstractControl):ValidationErrors|null => {
let password = control.get('password');
let confirm_password = control.get('confirm_password');
if (password && confirm_password && password?.value != confirm_password?.value) {
return { password_match : true }
}
return null;
}
In componentsregistrationregistration.component.ts
I have:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { matchpassword } from "../../custom-validators/matchpassword.validator";
@Component({
selector: 'app-registration',
templateUrl: './registration.component.html',
styleUrls: ['./registration.component.scss']
})
export class RegistrationComponent implements OnInit {
public registrationForm!: FormGroup;
public isSuccess: Boolean = false;
constructor(private formBuilder: FormBuilder) { }
get form() { return this.registrationForm.controls }
public registerUser() {
if (this.registrationForm.status !== 'INVALID') {
// Show success alert
this.isSuccess = true;
// Reset form
this.registrationForm.reset();
} else {
return;
}
}
ngOnInit() {
this.registrationForm = this.formBuilder.group({
firstName: ['', [Validators.required, Validators.minLength(3)]],
lastName: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(6), matchpassword]],
confirm_password: ['', matchpassword],
terms: ['', Validators.requiredTrue],
}, {
validators: matchpassword
});
}
}
In componentsregistrationregistration.component.html
I have:
<div *ngIf="isSuccess" class="alert alert-success alert-dismissible fade show text-center">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
Your signup was successful!
</div>
<form
[formGroup]="registrationForm"
(ngSubmit)="registerUser()"
novalidate
>
<div class="form-element mb-2">
<label for="firstName" class="form-label"> First name </label>
<input
type="text"
id="firstName"
formControlName="firstName"
class="form-control form-control-sm"
/>
<span
class="invalid-feedback"
*ngIf="form['firstName']?.touched && form['firstName'].errors?.['required']"
>The <i>First name</i> field is required</span
>
<span
class="invalid-feedback"
*ngIf="form['firstName'].touched && form['firstName'].errors?.['minlength']"
>The <i>First name</i> must be at least 3 characters long</span
>
</div>
<div class="form-element mb-2">
<label for="lastName" class="form-label"> Last name </label>
<input
type="text"
id="lastName"
formControlName="lastName"
class="form-control form-control-sm"
/>
<span
class="invalid-feedback"
*ngIf="form['lastName'].touched && form['lastName'].errors?.['required']"
>The <i>Last name</i> field is required</span
>
<span
class="invalid-feedback"
*ngIf="form['lastName'].touched && form['lastName'].errors?.['minlength']"
>The <i>Last name</i> must be at least 3 characters long</span
>
</div>
<div class="form-element mb-2">
<label for="email" class="form-label"> Email address </label>
<input
type="email"
id="email"
formControlName="email"
class="form-control form-control-sm"
/>
<span
class="invalid-feedback"
*ngIf="form['email'].touched && form['email'].errors?.['required']"
>The <i>Email</i> field is required</span
>
<span
class="invalid-feedback"
*ngIf="form['email'].touched && form['email'].errors?.['email']"
>Please provide a valid email address</span
>
</div>
<div class="form-element mb-2">
<label for="password" class="form-label"> Password </label>
<input
type="password"
id="password"
formControlName="password"
class="form-control form-control-sm"
/>
<span
class="invalid-feedback"
*ngIf="form['password'].touched && form['password'].errors?.['required']"
>The <i>Password</i> field is required</span
>
<span
class="invalid-feedback"
*ngIf="form['password'].touched && form['password'].errors?.['minlength']"
>The password must have al least 6 characters</span
>
<span
class="invalid-feedback"
*ngIf="form['password'].touched && form['password'].errors?.['passwordcomplexity']"
>The password is not complex enough</span
>
<span
class="invalid-feedback"
*ngIf="form['password'].touched && registrationForm.errors?.['password_match']
"
>The passwords do not match</span
>
</div>
<div class="form-element mb-2">
<label for="confirm_password" class="form-label">
Confirm password
</label>
<input
type="password"
id="confirm_password"
formControlName="confirm_password"
class="form-control form-control-sm"
/>
</div>
<div class="form-element mb-2">
<input
type="checkbox"
formControlName="terms"
id="terms"
class="me-1"
/>
<span class="text-terms text-muted"
>I accept the
<a href="#" class="text-success">Terms & conditions</a></span
>
<span
class="invalid-feedback terms"
*ngIf="form['terms'].dirty && form['terms'].errors?.['required']"
>You must accept our Terms & conditions</span
>
</div>
<div class="pt-2">
<button
type="submit"
class="btn btn-sm btn-success w-100"
[disabled]="!registrationForm.valid"
>
Submit
</button>
</div>
</form>
Stackblitz
There is a stackblitz with all the code HERE
The goal
If the passwords do not match, I want the error message "The passwords do not match" to show on the password field, as below:
The problem
Although the password is seen as invalid (the submit button is disabled), the error message does not show:
What am I doing wrong?
2
Answers
In case somebody might find the complete code that worked for me...
The custom validators
custom-validatorsmatchpassword.validator.ts
and
custom-validatorspasswordcomplexity.validator.ts
In
appcomponentsregistrationregistration.component.ts
I have:In
srcappcomponentsregistrationregistration.component.html
I have:In order to prevent validation messages overlap, in the SCSS I do this to display only the first message in the DOM:
DEMO
There is demo stackblitz HERE.
The
matchpassword
validator needs to be added to the form group rather than to some single control. Adding a validator to a control doesn’t give you access to the other controls.After fixing that, you need to adjust your error message to be displayed when the form group has an error rather than the form control:
Updated stackblitz: https://stackblitz.com/edit/stackblitz-starters-wbynzu?file=src%2Fapp%2Fcomponents%2Fregistration%2Fregistration.component.ts
Note: There are now some issues with overlapping error messages, which I didn’t fix, as this is not related to the question