components icon indicating copy to clipboard operation
components copied to clipboard

Autocomplete with multiple selection (MdAutocompleteTrigger customization)

Open arlowhite opened this issue 8 years ago • 15 comments

Feature 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:

  1. not deselect other MdOptions on selection
  2. 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.

arlowhite avatar Jun 09 '17 21:06 arlowhite

@jelbourn Is someone planning to work on a multiple autocomplete? Or would you be open to me creating a new MdMultipleAutocompleteTrigger?

arlowhite avatar Jun 12 '17 22:06 arlowhite

No plans to work on this, would need some discussion to determine if we want to add it to the library. The a11y would be problematic since an autocomplete is a role="combobox" and I'm not sure if that supports multiple values.

jelbourn avatar Jun 12 '17 22:06 jelbourn

@jelbourn Don't know much about a11y, but MdSelect uses 'role': 'listbox', anything wrong with changing MdAutocomplete's role to listbox for multiple mode or does that cause problems?

arlowhite avatar Jun 12 '17 23:06 arlowhite

A listbox doesn't have a text input like autocomplete does. See https://www.w3.org/TR/wai-aria/roles#combobox

Looks like combobox theoretically supports being associated with a listbox with multiple selection, but it would need some testing; screen-readers are often "best effort" on certain boundary cases in the spec

jelbourn avatar Jun 12 '17 23:06 jelbourn

I am also in need of this function. Either a customization or a new component "multi-search-select" will work. I would imagine something close to this, https://semantic-ui.com/modules/dropdown.html#multiple-search-selection

Aaron-Zhao avatar Aug 15 '17 01:08 Aaron-Zhao

It is good to have functionality especially in corporate application where there is always a need of this component

bharathmuppa avatar Oct 03 '17 13:10 bharathmuppa

I want this :) I created own component, but good to see native implementation :)

PawelOwczarekFalcon avatar Oct 18 '17 13:10 PawelOwczarekFalcon

@PawelOwczarekFalcon is your component open sourced? :)

george-oakling avatar Nov 14 '17 15:11 george-oakling

@Aaron-Zhao The Semantic UI example is functionally more like chips with an autocomplete. There is some work on combining MatChipList and MatFormField https://material2-dev.firebaseapp.com/chips See this issue comment https://github.com/angular/material2/issues/3273#issuecomment-327110530 They claim an Autocomplete should work, but I haven't tried it yet. I'm not sure what the official status on this is.

However, if you want it to look more like Semantic UI you'll need to do some re-styling with CSS.

IMO, the advantage of Chips + Autocomplete is that you clearly see all selected values. However, it takes much more space.

The advantage of the Autocomplete+multiple hack I created is that it takes less space. However, it's a bit confusing to users. The fundamental problem is that the input text is your search AND the display of selected values (after blur), which is confusing. And there's also the accessibility issues that @jelbourn mentioned.

So I think going with Chips+Autocomplete is probably better. For my use case, I'll just create more compact styling since my App isn't used on touch devices where the big chips are needed.

@jelbourn I'm tempted to close this issue unless someone has a design for multiple+Autocomplete that isn't equivalent to Chips+Autocomplete.

arlowhite avatar Nov 15 '17 01:11 arlowhite

@arlowhite What about extending the mat-select to be searchable/filterable while multiple selection is enabled. Does that accomplish the same purpose? There's a pr open for a mat-select-header https://github.com/angular/material2/pull/7835 where you could add a search input to filter the options. The author of the pull requests is waiting for feedback because I think he want's to make sure @jelbourn's a11y concerns are addressed.

jrood avatar Dec 06 '17 21:12 jrood

@jrood Extending mat-select approach looks clean.

singhkis14 avatar Mar 04 '18 07:03 singhkis14

Here is how i handled it https://stackblitz.com/edit/angular-v1b716 I used two form controls. One for the autocomplete and one for multiple selection which I managed manually.

irinelpascu avatar May 15 '18 11:05 irinelpascu

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

sandeeppatidar30 avatar Oct 17 '18 10:10 sandeeppatidar30

Here is how i handled it https://stackblitz.com/edit/angular-v1b716 I used two form controls. One for the autocomplete and one for multiple selection which I managed manually.

i took a reference from your solution. could you let us know how to bind the selected values as in mat select like for e.g. i tried ([value])="somearray"

dandydarshan avatar Nov 12 '18 14:11 dandydarshan

I'll just have to say @sandeeppatidar30 solution is what I went with and there were no issues. It looks slightly different than it would with MatOptions and it's not 100% visually "correct" but it's a great solution to this issue. Still can't believe Material does not have a searchable multiselect autocomplete.

Cubelaster avatar May 09 '25 05:05 Cubelaster