Angular Material の複数選択オートコンプリート


最近のクライアントの場合、「検索可能な」選択が必要でした.彼らは、他のアプリケーションで使用されている機能と一致させたいと考えていました.元の検索可能な選択は、最新の Angular アプリケーションにうまく適合しない従来の jQuery オブジェクトでした.

私が必要としていたのは、複数行の選択を可能にする選択タイプのドロップダウンと、ユーザーが検索として入力した文字列でリストを絞り込む機能でした.

これが私が思いついたものです...複数選択のオートコンプリートです.

コード



Working Example
GitHub Repo

HTML



HTML から始めて... これらは、背後にあるロジックをより理解しやすくするために順不同で表示されます.

入力



これは、 Material の入力が selectControl に関連付けられたフォーム フィールドです.

<mat-form-field class="full-width">
  <input matInput type="text"
  [placeholder]="placeholder"
  [matAutocomplete]="auto"
  [formControl]="selectControl">
</mat-form-field>


チップリスト



選択を表示するために Material チップ リストを追加しました.通常、このコードは他のコードの上にあるため、オートコンプリート ドロップダウンの下に隠れません.このリストでは、クリックでチップを削除することもできます.

<div class="chip-list-wrapper">
  <mat-chip-list #chipList>
    <ng-container *ngFor="let select of selectData">
      <mat-chip class="cardinal-colors" (click)="removeChip(select)">
        {{ select.item }}
        <mat-icon class="mat-chip-remove">cancel</mat-icon>
      </mat-chip>
    </ng-container>
  </mat-chip-list>
</div>


オートコンプリート



そして、これは filterdata に関連付けられた Material オートコンプリートです.

<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
  <mat-option *ngFor="let data of filteredData | async">
    <div (click)="optionClicked($event, data)">
      <mat-checkbox [checked]="data.selected" 
        (change)="toggleSelection(data)" 
        (click)="$event.stopPropagation()">
        {{ data.item }}
      </mat-checkbox>
    </div>
  </mat-option>
</mat-autocomplete>


CSS



CSS は非常に簡単です...サイズと色がいくつかあります.

.full-width {
  width: 100%;
}

.chip-list-wrapper {
  min-height: 3em;
}

.msac-colors {
  background-color: var(--primary-color);
  color: white;
}


TypeScript



繰り返しますが、読みやすくするためにこのコードを分割してみます.

輸入品




import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

import { ItemData } from '@core/interfaces/multi-select-item-data';


これらのほとんどは非常に簡単です... ItemDataには定義が必要です...インターフェースを見て...

export interface ItemData {
  item: string;
  selected: boolean;
}


コンポーネントラッパー




@Component({
  selector: 'multiselect-autocomplete',
  templateUrl: './multiselect-autocomplete.component.html',
  styleUrls: ['./multiselect-autocomplete.component.scss']
})
export class MultiselectAutocompleteComponent implements OnInit {
  ...
}


データ設定



データポイント、入力、および出力は次のとおりです.

@Output() result = new EventEmitter<{ key: string, data: Array<string> }>();

@Input() placeholder: string = 'Select Data';
@Input() data: Array<string> = [];
@Input() key: string = '';

selectControl = new FormControl();

rawData: Array<ItemData> = [];
selectData: Array<ItemData> = [];

filteredData: Observable<Array<ItemData>>;
filterString: string = '';

placeholderdata の構造はかなり明確です. key が渡され、変更されずに出力されます.これにより、外部の (呼び出し元の) コードは、どのオブジェクトにアタッチするかを知ることができます.

初期化




constructor() {
  this.filteredData = this.selectControl.valueChanges.pipe(
    startWith<string>(''),
    map(value => typeof value === 'string' ? value : this.filterString),
    map(filter => this.filter(filter))
  );
}

ngOnInit(): void {
  this.data.forEach((item: string) => {
    this.rawData.push({ item, selected: false });
  });
}


今、私は data 入力を取り、ブール値として選択された一致する rawData を生成しています.

さらに、filteredDataselectControl 値の変更にバインドしています.これが、上記の HTML で async が必要な理由です.

フィルタおよび表示機能



これら 2 つの関数は、上記の HTML オブジェクトで直接使用されます.

filter = (filter: string): Array<ItemData> => {
  this.filterString = filter;
  if (filter.length > 0) {
    return this.rawData.filter(option => {
      return option.item.toLowerCase().indexOf(filter.toLowerCase()) >= 0;
    });
  } else {
    return this.rawData.slice();
  }
};

displayFn = (): string => '';


オプションがクリックされました




optionClicked = (event: Event, data: ItemData): void => {
  event.stopPropagation();
  this.toggleSelection(data);
};

optionClicked は、読みやすくするためにこのように名前が付けられ、構成されています.

選択を切り替え




toggleSelection = (data: ItemData): void => {
  data.selected = !data.selected;

  if (data.selected === true) {
    this.selectData.push(data);
  } else {
    const i = this.selectData.findIndex(value => value.item === data.item);
    this.selectData.splice(i, 1);
  }

  this.selectControl.setValue(this.selectData);
  this.emitAdjustedData();
};

toggleSelection はトグルし、 selectData から値を追加/削除し、変更されたデータを出力します.

調整済みデータの発行




emitAdjustedData = (): void => {
  const results: Array<string> = []
  this.selectData.forEach((data: ItemData) => {
    results.push(data.item);
  });
  this.result.emit({ key: this.key, data: results });
};


ここでは、選択した項目のみを含む単純な文字列の配列を再構築する必要がありました.

チップの取り外し



このコードは冗長に思えますが、私の考えでは、機能を明確に記述した方がよいでしょう.

removeChip = (data: ItemData): void => {
  this.toggleSelection(data);
};


複数選択オートコンプリートの使用



HTML



ここでは、入力を渡し、出力された result をキャプチャする関数を設定しました.

<multiselect-autocomplete
  [placeholder]="structure[index].subtitle"
  [data]="cardSelects[card.key]"
  [key]="card.key"
  (result)="selectChange($event)">
</multiselect-autocomplete>


TypeScript



イベント keydata が出力され、ここで使用されます.

selectChange = (event: any) => {
  const key: string = event.key;
  this.cardValue[key] = [ ...event.data ];
};


コード



Working Example
GitHub Repo

概要



これは作成するのにクールなコンポーネントであり、良い挑戦でした.見た目だけでなく、機能性にも満足しています.