I'm trying to create a form with an input using chips to send a message to various emails. I have it almost functional but I don't know how to properly show the message error when you write an invalid email or don't write anything.
This is my custom validator:
import { FormControl } from '@angular/forms';
const REGEXP_EMAIL =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
export const emailsArrayValidator = (control: FormControl) => {
const emails: string[] = Array.isArray(control.value) ? control.value : [];
const invalidEmails = emails.filter(email => !REGEXP_EMAIL.test(email.trim()));
return invalidEmails.length === 0 ? null : { invalidEmails: true };
};
This is the component.ts:
constructor(private fb: FormBuilder) {
this.inviteForm = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.pattern(this.REGEXP_EMAIL)]],
date: ['', Validators.required],
friends: [[], [Validators.required, Validators.minLength(1), emailsArrayValidator]],
});
}
get name() {
return this.inviteForm.get('name') as FormControl;
}
get email() {
return this.inviteForm.get('email') as FormControl;
}
get date() {
return this.inviteForm.get('date') as FormControl;
}
get friends() {
return this.inviteForm.get('friends') as FormControl;
}
addFriend(event: MatChipInputEvent) {
const friendEmail = event.value.trim();
const friends = [...this.friends.value, friendEmail];
const validationErrors = emailsArrayValidator(new FormControl(friends));
console.log(validationErrors);
if (validationErrors) {
this.friendsErrorMessage = 'The last email is not valid';
} else {
this.friendsErrorMessage = '';
this.friends.setValue(friends);
}
event.chipInput!.clear();
}
removeFriend(email: string) {
const friends = this.friends.value.slice();
const index = friends.indexOf(email);
if (index !== -1) {
friends.splice(index, 1);
this.friends.setValue(friends);
}
}
And this is the HTML:
<mat-form-field>
<mat-label>Enter invitation email</mat-label>
<mat-chip-grid #chipGrid>
@for (friend of friends.value; track friend) {
<mat-chip-row (removed)="removeFriend(friend)" [editable]="true">
{{ friend }}
<button matChipRemove>
<mat-icon>cancel</mat-icon>
</button>
</mat-chip-row>
}
<input
[matChipInputFor]="chipGrid"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="addOnBlur"
(matChipInputTokenEnd)="addFriend($event)"
required
/></mat-chip-grid>
@if (friends.errors && friends.touched) {
<mat-error>{{ friendsErrorMessage }}</mat-error>
}
</mat-form-field>
Your issue is similar to this GitHub issue: mat-error with MatChipInput is not displayed under the input.
errorState = true
to show the error message.friends
control via <AbstractControl>.setErrors(/* ValidationError */)
.friends
control as touched via <AbstractControl>.markAsTouched()
to display the error message as your condition: @if (friends.errors && friends.touched)
.Besides, you should also reset the errorState
and remove the validation error for the scenario where the invalid email is inputted previously and then fill in the valid email.
import {MatChipGrid} from '@angular/material/chips';
@ViewChild('chipGrid') chipGrid: MatChipGrid;
addFriend(event: MatChipInputEvent) {
const friendEmail = event.value.trim();
const friends = [...this.friends.value, friendEmail];
const validationErrors = emailsArrayValidator(new FormControl(friends));
console.log(validationErrors);
if (validationErrors) {
this.friendsErrorMessage = 'The last email is not valid';
this.friends.setErrors(validationErrors, { emitEvent: false })
this.chipGrid.errorState = true;
} else {
this.chipGrid.errorState = false;
this.friends.setErrors({ invalidEmails: null });
this.friendsErrorMessage = '';
this.friends.setValue(friends);
}
this.friends.markAsTouched();
event.chipInput!.clear();
}