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);
  }

}