In this post, we’ll look at how to validate autocomplete component from Angular Material. Working with reactive forms, we’ll write a custom validator to force selection only from the list of options.
Table of contents
The autocomplete component is an input with a dropdown of suggested options. It allows the user to type into the input and filter list of options. Then, the user can choose an option.
Unfortunately, it does not validate text value against the list of options.
Create custom validator for autocomplete
Working with reactive forms, the easiest way to solve this issue is to write a custom form validator.
Firstly, let’s create a new file requireMatch.ts
:
import { AbstractControl } from '@angular/forms';
export function RequireMatch(control: AbstractControl) {
const selection: any = control.value;
if (typeof selection === 'string') {
return { incorrect: true };
}
return null;
}
Code language: TypeScript (typescript)
This function evaluates the value of a control and return a validation error if the value is a string
type.
In detail, AbstractControl
parameter allows us to read the current value of any form control. If the type of value is just text, we return an error code incorrect
. Otherwise, we return null to indicate no errors.
All form controls can make use of this validator function.
Use validator in form
We can now import the function into your component and pass it into the form control constructor.
In the snippet below, I created a simple FormGroup
with an autocomplete control to select a project from a list. In addition to adding a required validator, we specify RequireMatch
validator on the validators array.
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { Project } from './project';
import { RequireMatch as RequireMatch } from './requireMatch';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
form: FormGroup;
projects: Project[] = [
new Project("Web Development"),
new Project("UX"),
new Project("SEO")
];
ngOnInit() {
this.form = new FormGroup({
project: new FormControl('', [Validators.required, RequireMatch]),
});
}
displayWith(obj?: any): string | undefined {
return obj ? obj.name : undefined;
}
}
Code language: TypeScript (typescript)
With the form group is configured, we are now able to display an error message in the template:
<form [formGroup]="form">
<mat-form-field class="full-width">
<input type="text" placeholder="Project" matInput formControlName="project"
[matAutocomplete]="projectAutoComplete">
<mat-autocomplete #projectAutoComplete="matAutocomplete" [displayWith]="displayWith">
<mat-option *ngFor="let project of projects" [value]="project">
{{ project.name }}
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="form.controls['project'].hasError('required')">
Please enter a value
</mat-error>
<mat-error *ngIf="form.controls['project'].hasError('incorrect')">
Please select a valid project
</mat-error>
</mat-form-field>
</form>
Code language: HTML, XML (xml)
Source code is available on GitHub.
Summary
In summary, I explained how to implement a custom validator to force option selection for Angular Material autocomplete component. This validator basically makes the autocomplete a hybrid between a select and an autocomplete.
well my auto complete is dynamic and I am getting data from database so following part is giving error.
Could you tell me how to deal with it.
displayWith(obj?: any): string | undefined {
return obj ? obj.name : undefined;
}
Like!! Great article post.Really thank you! Really Cool.
https://stackblitz.com/edit/angular-hph5yz
This solved my problem. It includes filtering too.
Basically I am checking for a filter feature enabled with this approach, could you please do the needful, Thanks.
This works well. Thanks!
I’ve a problem though, with the given example the Autocomplete is loaded well but is not searchable. To make it searchable I’ve tried using [formControl] on top of existing code as below and the list just doesn’t load at all, what am I missing?
Template:
{{skillItem.name}}
Please enter a value
Please select a valid skill
Component:
this.form = new FormGroup({
skill: new FormControl(”, [Validators.required, this.RequireMatch]),
});
this.filteredSkillData = this.skillMatAutocompleteControl.valueChanges
.pipe(
startWith(null),
map(name => name ? this._filter(name) : this.skillData.slice())
);
private _filter(value: any): Skill[] {
value = (value && typeof value === ‘string’) ? value : value.name;
const filterValue = value.toLowerCase();
return this.skillData.filter(skill => skill.name.toLowerCase().includes(filterValue));
}
while submit form i am not getting required error
Hi, do you see any errors in the console (hit F12 to see)? Also, make sure the control key matches your form control defined in the component.
How can I apply an Angular Material Autocomplete Force Selection solution using template-driven forms?
Thanks for this easy solution!
It would also be nice to match on an object of type x instead of just object.
Also do you know how how to match the data with and id of an object and not entire object? I save the id’s in my database.
Thanks for this solution, i found it quite clean and simple
Your validator can only be used once on one autocomplete and will not work on second autocomplete?
You can register the validator with as many autocomplete form controls as you wish!
When I enter a valid projectname – not by selection – it is rejected as well. I think the user will not understand this behaviour. Anyway, the input and the autocomplete – being aware of each other – should tackle this in the first place, f.e. after loosing focus. But maybe it can and I missed some property.
You’re correct, typing in a valid value does not automatically select the option – the user must click on an option or use keyboard arrow keys to highlight & press the enter key. Unfortunately, this appears to be the behaviour of the autocomplete component.
Feel free to create a PR for this or raise this as an issue at https://github.com/angular/material
It doesn’t work with [value]=”project.name”
Hi Ankur, thank you for your comment. What error message are you getting?