Is there a way to make a multiselection in Autocomplete (Angular4+)?
Emily Wong
I want to make a multi selection on filtered items by the auto complete. Inspired from the following tutorialI tried this code:
The component :
<form> <mat-form-field> <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto"> <mat-autocomplete #auto="matAutocomplete"> <mat-option *ngFor="let option of filteredOptions | async" [value]="option" multiple> <mat-checkbox> {{ option }} </mat-checkbox> </mat-option> </mat-autocomplete> </mat-form-field>
</form>I added tag to enable selection but it does not work. Once I filter and select one option the menu closes and the checkbox doesn't even get checked. Is there a way to make a multiselection in Autocomplete? Thank you !!
4 Answers
Angular Material documentation for chips includes a nice example of how to get started with a multiple selection autocomplete:
<mat-form-field> <mat-chip-list #chipList> <mat-chip *ngFor="let fruit of fruits" [selectable]="selectable" [removable]="removable" (removed)="remove(fruit)"> {{fruit}} <mat-icon matChipRemove *ngIf="removable">cancel</mat-icon> </mat-chip> <input placeholder="New fruit..." #fruitInput [formControl]="fruitCtrl" [matAutocomplete]="auto" [matChipInputFor]="chipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="addOnBlur" (matChipInputTokenEnd)="add($event)"> </mat-chip-list> <mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)"> <mat-option *ngFor="let fruit of filteredFruits | async" [value]="fruit"> {{fruit}} </mat-option> </mat-autocomplete>
</mat-form-field>Basically, you have a chip list bound to a list and an text input that allows to search within an autocomplete list.
import {COMMA, ENTER} from '@angular/cdk/keycodes';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {FormControl} from '@angular/forms';
import {MatAutocompleteSelectedEvent, MatChipInputEvent} from '@angular/material';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
/** * @title Chips Autocomplete */
@Component({ selector: 'chips-autocomplete-example', templateUrl: 'chips-autocomplete-example.html', styleUrls: ['chips-autocomplete-example.css'],
})
export class ChipsAutocompleteExample { visible = true; selectable = true; removable = true; addOnBlur = false; separatorKeysCodes: number[] = [ENTER, COMMA]; fruitCtrl = new FormControl(); filteredFruits: Observable<string[]>; fruits: string[] = ['Lemon']; allFruits: string[] = ['Apple', 'Lemon', 'Lime', 'Orange', 'Strawberry']; @ViewChild('fruitInput') fruitInput: ElementRef; constructor() { this.filteredFruits = this.fruitCtrl.valueChanges.pipe( startWith(null), map((fruit: string | null) => fruit ? this._filter(fruit) : this.allFruits.slice())); } add(event: MatChipInputEvent): void { const input = event.input; const value = event.value; // Add our fruit if ((value || '').trim()) { this.fruits.push(value.trim()); } // Reset the input value if (input) { input.value = ''; } this.fruitCtrl.setValue(null); } remove(fruit: string): void { const index = this.fruits.indexOf(fruit); if (index >= 0) { this.fruits.splice(index, 1); } } selected(event: MatAutocompleteSelectedEvent): void { this.fruits.push(event.option.viewValue); this.fruitInput.nativeElement.value = ''; this.fruitCtrl.setValue(null); } private _filter(value: string): string[] { const filterValue = value.toLowerCase(); return this.allFruits.filter(fruit => fruit.toLowerCase().indexOf(filterValue) === 0); }
}The code is pretty straightforward (filter based on text input after each typed character, remove an item from the list etc.). Of course, this should be tweaked to your needs (e.g. once you select an item, it should be filtered out for next autocomplete results).
3This is a little late to the party but I found an excellent stackBlitz here . Reproducing the code here, written by the owner:
HTML:
<mat-form-field> <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>Typescript:
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); }
} 2 I would suggest using Ng2-select:
<ng-select [virtualScroll]="true" #select [items]="items" [multiple]="true" bindLabel="name" placeholder="items..." formControlName="myFormCOntrolName"> </ng-select>It is styled using angular material. The virtualScroll option isn't well documented at present, but is very fast for large data sets.
Ng2-select, try the multiple one, , let me know if this is what you want.
In general, there's no good standard for multiple selection.
1