I wish Nehal's solution worked, but I couldn't get it to. Came up with my solution after working with @AJT_82 code a bit. My need really came from wanting to check passwords as a group and his solution didn't cover that. So, I have included the other peieces I used to create the full solution that worked for me.
I came accross this problem attempting to do validation of password confirmation in angular 4 after discovering that the method I had used in 2 no longer worked the same. The information on angular.io didn't really help much for this as a lot of the information is fragmented across different areas of their documention.
So, to clarify, this follows Angular's reactive form method. I wrote this to validate passwords in an instance where I also had other validation restrictions (password must be between 4 and 24 characters, is required, etc). This method should work just as well for email confirmation with a few small tweaks.
First, in order to compare validators for a group, the html form must have a subgroup identified using the formGroupName=" " identifier. This is within the primary [formGroup] identifier. Compared inputs must be inside this formGroupName labeled element. In my case it is just a div.
<div class="title">Hero Registration Form</div>
<form [formGroup]="regForm" id="regForm" (ngSubmit)="onSubmit()">
<div class="input">
<label for="heroname">Heroname</label> <input type="text"
id="heroname" class="form-control" formControlName="heroname"
required />
<div *ngIf="formErrors.heroname" class="hero-reg-alert">{{
formErrors.heroname }}</div>
</div>
<div class="input">
<label for="email">Email</label> <input type="email" id="email"
class="form-control" formControlName="email" required />
<div *ngIf="formErrors.email" class="hero-reg-alert">{{
formErrors.email }}</div>
</div>
<div formGroupName="password">
<div class="input">
<label for="password1">Password</label> <input type="password"
id="password1" class="form-control" formControlName="password1"
required />
<div *ngIf="formErrors.password.password1" class="hero-reg-alert">
{{ formErrors.password.password1 }}</div>
</div>
<div class="input">
<label for="password2">Re-Enter Password</label> <input
type="password" id="password2" class="form-control"
formControlName="password2" required />
<div
*ngIf="formErrors.password.password2 || formErrors.password.password"
class="hero-reg-alert">{{ formErrors.password.password }} {{
formErrors.password.password2 }}</div>
</div>
</div>
<button type="submit" [disabled]="!regForm.valid">
<span id="right-btntxt">SUBMIT</span>
</button>
</form>
You may notice that I have subvalues for formErrors as
formErrors.password.password
formErrors.password.password1
formErrors.password.password2
These are built in my code as so...
formErrors = {
'username': '',
'email': '',
'password': {
'password': '',
'password1': '',
'password2': ''
}
};
I built it this way as 'password1' and 'password2' hold my formErrors for each field, while 'password' holds my error in the case of a mismatch (where the two entered passwords are not equal).
Here is may onValueChanged() formula. It checks if a field is an instanceof FormGroup. If so, it first checks the custom validation for that field group first (storing them in 'this.formErrors[field][field]' ), then proceeds to handle subfield validations. In the case of it not being an instanceof FieldGroup, validation is handled as per the example on angular.io's guideance doc.
onValueChanged(data?: any) {
if (!this.regForm) {return;}
const form = this.regForm;
for (const field in this.formErrors) {
const formControl = form.get(field);
if (formControl instanceof FormGroup) {
this.formErrors[field][field] = '';
// check for custom validation on field group
const control = form.get(field);
// handle validation for field group
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field][field] += messages[key] + ' ';
}
}
// handle validation for subfields
for (const subfield in formControl.controls) {
console.log('SUBFIELD', subfield);
this.formErrors[field][subfield] = '';
const control = formControl.controls[subfield];
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[subfield];
for (const key in control.errors) {
this.formErrors[field][subfield] += messages[key] + ' ';
}
}
}
}
// alternate validation handling for fields without subfields (AKA not in a group)
else {
const control = form.get(field);
this.formErrors[field] = '';
if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field];
for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' ';
}
}
}
}
}
By giving the FieldGroup a subfield with it's own name, we can store the validations on the FieldGroup. Attempting to do this with the regular onValueChange code overwrites the subfields at the line...
this.formErrors[field] = '';
Not providing a place to store the FieldGroup validation either overwrites the subfields or doesn't handle the FieldGroup.
Should you need it, this is how the form is built using buildFomr();
buildForm(): void {
this.regForm = this.formBuilder.group({
'username': [this.registerUser.username, [
Validators.required,
Validators.minLength(4),
Validators.maxLength(24)
]
],
'email': [this.registerUser.email, [
Validators.email,
Validators.required
]
],
'password': this.formBuilder.group({
'password1': [this.registerUser.password, [
Validators.required,
Validators.minLength(8),
Validators.maxLength(24)
]
],
'password2': ['', [
Validators.required
]
]
}, {validator: this.passwordMatchValidator})
}); // end buildForm
this.regForm.valueChanges
.subscribe(data => this.onValueChanged(data));
this.onValueChanged(); // (re)set validation messages now
}
and this is the custom validation function...
passwordMatchValidator(control: FormGroup): {[key: string]: any} {
return control.get('password1').value !== control.get('password2').value ? {mismatch: true} : null;
}