How do I implement autocomplete in a <mat-select> component?
One way is to add a manual filter inside mat-select so it will have auto-complete functionality. Find the example code of below given solution here
add a input controller for filter text
public filterControl = new FormControl();
add the filter text field inside mat-select
<mat-select
[id]="fieldId"
[formControl]="custonDropdown"
(selectionChange)="onSelectChange($event.value)"
(closed)="onPanelClose()"
multiple
panelClass="custom-mat-select">
<input matInput
[formControl]="filterControl"
type="text"
name="filter-options"
id="filter-options"
placeholder="Search">
<mat-option *ngFor="let option of filteredOptions | async"
[value]="option.value"
[ngClass]="{'hide': !option.show}">
{{option.name | translate}}
</mat-option>
</mat-select>
now add a event listener for filter text field value change
this.filteredOptions = this.filterControl.valueChanges.pipe(
startWith(''),
map((value: string) => {
this.optionItems
.forEach(option => {
option.show = option.name.toLocaleLowerCase().includes(value.toLowerCase());
});
return this.optionItems;
})
);
also to make the whole list to be view in next dropdown open clear the filter text field on panel close, as mentioned in the template code (closed)="onPanelClose()"
onPanelClose() {
this.filterControl.setValue('');
}
Demo
Yes you can use PrimeNG multiselect control. Only issue with that is if you are worried about themes.
There is another option using mat-select-autocomplet
How to install:
npm install select-autocomplete --save
npm: https://www.npmjs.com/package/mat-select-autocomplete
Demo: https://stackblitz.com/edit/mat-select-autocomplete
You can implement an autocomplete multi-select using MatAutocomplete
and some tricks. I'm fairly sure you can't do it with MatSelect
since that doesn't give you control over the input element. The technique is a little odd, but it works fine. The idea is to manage selections in your controller, and set the "value" of each mat-option
to the same thing - your selected options. You also need to trap clicks to allow users to interact with the list (rather than have it close immediately when clicked), and of course provide checkboxes inside the mat-option
items. A few other tricks are necessary as well - here is a quick and dirty example that shows what to do.
HTML:
<mat-form-field class="example-full-width">
<input type="text" placeholder="Select Users" aria-label="Select Users" matInput [matAutocomplete]="auto" [formControl]="userControl">
<mat-hint>Enter text to find users by name</mat-hint>
</mat-form-field>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let user of filteredUsers | async" [value]="selectedUsers">
<div (click)="optionClicked($event, user)">
<mat-checkbox [checked]="user.selected" (change)="toggleSelection(user)" (click)="$event.stopPropagation()">
{{ user.firstname }} {{ user.lastname }}
</mat-checkbox>
</div>
</mat-option>
</mat-autocomplete>
<br><br>
<label>Selected Users:</label>
<mat-list dense>
<mat-list-item *ngIf="selectedUsers?.length === 0">(None)</mat-list-item>
<mat-list-item *ngFor="let user of selectedUsers">
{{ user.firstname }} {{ user.lastname }}
</mat-list-item>
</mat-list>
TS:
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
export class User {
constructor(public firstname: string, public lastname: string, public selected?: boolean) {
if (selected === undefined) selected = false;
}
}
/**
* @title Multi-select autocomplete
*/
@Component({
selector: 'multiselect-autocomplete-example',
templateUrl: 'multiselect-autocomplete-example.html',
styleUrls: ['multiselect-autocomplete-example.css']
})
export class MultiselectAutocompleteExample implements OnInit {
userControl = new FormControl();
users = [
new User('Misha', 'Arnold'),
new User('Felix', 'Godines'),
new User('Odessa', 'Thorton'),
new User('Julianne', 'Gills'),
new User('Virgil', 'Hommel'),
new User('Justa', 'Betts'),
new User('Keely', 'Millington'),
new User('Blanca', 'Winzer'),
new User('Alejandrina', 'Pallas'),
new User('Rosy', 'Tippins'),
new User('Winona', 'Kerrick'),
new User('Reynaldo', 'Orchard'),
new User('Shawn', 'Counce'),
new User('Shemeka', 'Wittner'),
new User('Sheila', 'Sak'),
new User('Zola', 'Rodas'),
new User('Dena', 'Heilman'),
new User('Concepcion', 'Pickrell'),
new User('Marylynn', 'Berthiaume'),
new User('Howard', 'Lipton'),
new User('Maxine', 'Amon'),
new User('Iliana', 'Steck'),
new User('Laverna', 'Cessna'),
new User('Brittany', 'Rosch'),
new User('Esteban', 'Ohlinger'),
new User('Myron', 'Cotner'),
new User('Geri', 'Donner'),
new User('Minna', 'Ryckman'),
new User('Yi', 'Grieco'),
new User('Lloyd', 'Sneed'),
new User('Marquis', 'Willmon'),
new User('Lupita', 'Mattern'),
new User('Fernande', 'Shirk'),
new User('Eloise', 'Mccaffrey'),
new User('Abram', 'Hatter'),
new User('Karisa', 'Milera'),
new User('Bailey', 'Eno'),
new User('Juliane', 'Sinclair'),
new User('Giselle', 'Labuda'),
new User('Chelsie', 'Hy'),
new User('Catina', 'Wohlers'),
new User('Edris', 'Liberto'),
new User('Harry', 'Dossett'),
new User('Yasmin', 'Bohl'),
new User('Cheyenne', 'Ostlund'),
new User('Joannie', 'Greenley'),
new User('Sherril', 'Colin'),
new User('Mariann', 'Frasca'),
new User('Sena', 'Henningsen'),
new User('Cami', 'Ringo')
];
selectedUsers: User[] = new Array<User>();
filteredUsers: Observable<User[]>;
lastFilter: string = '';
ngOnInit() {
this.filteredUsers = this.userControl.valueChanges.pipe(
startWith<string | User[]>(''),
map(value => typeof value === 'string' ? value : this.lastFilter),
map(filter => this.filter(filter))
);
}
filter(filter: string): User[] {
this.lastFilter = filter;
if (filter) {
return this.users.filter(option => {
return option.firstname.toLowerCase().indexOf(filter.toLowerCase()) >= 0
|| option.lastname.toLowerCase().indexOf(filter.toLowerCase()) >= 0;
})
} else {
return this.users.slice();
}
}
displayFn(value: User[] | string): string | undefined {
let displayValue: string;
if (Array.isArray(value)) {
value.forEach((user, index) => {
if (index === 0) {
displayValue = user.firstname + ' ' + user.lastname;
} else {
displayValue += ', ' + user.firstname + ' ' + user.lastname;
}
});
} else {
displayValue = value;
}
return displayValue;
}
optionClicked(event: Event, user: User) {
event.stopPropagation();
this.toggleSelection(user);
}
toggleSelection(user: User) {
user.selected = !user.selected;
if (user.selected) {
this.selectedUsers.push(user);
} else {
const i = this.selectedUsers.findIndex(value => value.firstname === user.firstname && value.lastname === user.lastname);
this.selectedUsers.splice(i, 1);
}
this.userControl.setValue(this.selectedUsers);
}
}