angular / angular/components

Autocomplete with multiple selection (MdAutocompleteTrigger customization)

オープン
#5,053 コメント 15 件 リアクション 36 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

area: material/autocomplete feature P3
主要言語
TypeScript
スター
25k
フォーク
6.8k
平均マージ
1日 8時間
マージ済み PR(30日)
91

説明

#### 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 `MdOption`s on selection
2. leave the Autocomplete open after option selection event.

EDIT updated for beta12
```typescript
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.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

まず MdAutocompleteTrigger と、対応する MdSelect の複数選択ロジックを読み、選択のクリア、閉じる操作、issue で説明されている private メソッドに注目してください。MdAutocomplete が複数選択をサポートするか、monkey-patching を使わずにサポートされたカスタマイズ方法を提供し、選択済みのオプションを保持したまま選択後もパネルを開いた状態にできれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
angular, typescript
領域
frontend
issue の種類
機能追加
難易度
5/5
見積もり時間
1週間以上
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
25/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。