Autocomplete with multiple selection (MdAutocompleteTrigger customization)
See original GitHub issueFeature proposal:
It would be nice if MdAutocomplete
and MdAutocompleteTrigger
supported the multiple
attribute similar to MdSelect
, or at least configuration options that make creating your own easier.
What is the expected behavior?
It should be possible to create an Autocomplete with multiple selection.
What is the current behavior?
MdAutocompleteTrigger
is coded to assume single selection and is not easily customized, which makes a custom multi-autocomplete difficult to develop. Currently, you must “monkey-patch” private methods in MdAutocompleteTrigger
.
Which versions of Angular, Material, OS, TypeScript, browsers are affected?
@angular/cli: 1.0.4 node: 6.9.1 os: linux x64 @angular/common: 4.2.0 @angular/material: 2.0.0-beta.6-f89c6db @angular/cli: 1.0.4
Is there anything else we should know?
Here are my notes on the major issues I encountered in developing my own multiple Autocomplete.
In ngAfterContentInit
, I “monkey-patch” MdAutocompleteTrigger
. You cannot extend MdAutocompleteTrigger
because these methods are private.
The objective here is to:
- not deselect other
MdOption
s on selection - leave the Autocomplete open after option selection event.
EDIT updated for beta12
if (this.multiple) {
const self = this;
/*
easiest to just modify this MatAutoTrigger instance to get the behavior we want.
Hopefully, material2 will support this in the future
*/
const autoTrigger: any = this.mdAutoTrigger as any;
// make a no-op so other options aren't cleared when selecting an option
autoTrigger._clearPreviousSelectedOption = () => {};
// need to override to continue getting these events
// copied from material2/src/lib/autocomplete/autocomplete-trigger.ts with CHANGEs
autoTrigger._subscribeToClosingActions = function(this: any): Subscription {
const firstStable = first.call(this._zone.onStable.asObservable());
const optionChanges = RxChain.from(this.autocomplete.options.changes)
.call(doOperator, () => this._positionStrategy.recalculateLastPosition())
// Defer emitting to the stream until the next tick, because changing
// bindings in here will cause "changed after checked" errors.
.call(delay, 0)
.result();
// When the zone is stable initially, and when the option list changes...
return RxChain.from(merge(firstStable, optionChanges))
// create a new stream of panelClosingActions, replacing any previous streams
// that were created, and flatten it so our stream only emits closing events...
.call(switchMap, () => {
this._resetActiveItem();
this.autocomplete._setVisibility();
return this.panelClosingActions;
})
// when the first closing event occurs...
// CHANGE disable first() because we want to continue getting events
// .call(first)
// set the value, close the panel, and complete.
.subscribe(event => this._setValueAndClose(event));
};
// prevent closing on select option event
autoTrigger._setValueAndClose = function(this: any, event: MatOptionSelectionChange | null): void {
if (event && event.source) {
// CHANGE don't clear selection, clear input, or change focus
// this._clearPreviousSelectedOption(event.source);
// this._setTriggerValue(event.source.value);
this._onChange(event.source.value);
// this._element.nativeElement.focus();
this.autocomplete._emitSelectEvent(event.source);
}
// CHANGE added else clause (close non-MatOptionSelectionChange event)
else {
// NOTE this is the Subscription returned from _subscribeToClosingActions
// CHANGE unsubscribe from the Subscription created in _subscribeToClosingActions
this._closingActionsSubscription.unsubscribe();
this.closePanel();
// CHANGE clear input so placeholder can show selected values
self.clearInput();
}
};
}
Also, while working on this, I noticed that the code I was writing was very similar to MdSelect
and most of the logic is in MdAutocompleteTrigger
. So maybe what’s needed is a new MdMultiAutocompleteTrigger
, which uses the same SelectionModel
system that MdSelect
uses.
The one other issue is that even with ngFor trackBy
sometimes MdOption
instances lose their selected state. So every time the options are filtered, I double-check MdOption.selected
and select() deselect() as necessary.
Also, the way MdSelect
sets MdOption.multiple
seems awkward, but I basically do the same thing as the MdSelect
code.
FYI, my solution for displaying multiple values is just to clear the input, floatPlaceholder=‘never’ and set placeholder to the displayWith()
of each selected value separated by commas. This works fairly well. I also added a tooltip with the same content in case the entire text is not visible.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:35
- Comments:14 (3 by maintainers)
Top GitHub Comments
This is how i implemented https://stackblitz.com/edit/angular-xgtey4 using mat-chips, mat-autocomplete. Using above approach there is a flicker in autocomplete panel while selecting multiple items so i have used another approach https://stackblitz.com/edit/angular-ah51ss where i used mat-select-list in autocomplete instead of mat-option
It is good to have functionality especially in corporate application where there is always a need of this component