LoginSignup
0
0

More than 1 year has passed since last update.

Angular Materialでautocomplete

Last updated at Posted at 2022-06-20

はじめに

Angular Materialにはautocompleteというコンポーネントが存在します。
これは自由入力の内容に応じてセレクタの選択肢を絞り込めるものです。

オートコンプリート:https://material.angular.io/components/autocomplete/overview

しかしAngular Materialのautocompleteはそれほど使い勝手の良いものではなく、入力内容が選択肢のいずれとも一致しなくとも入力内容が残ってしまいます。
あくまで自由入力の補助であればこれでよいかと思いますが、セレクタのフィルタとしてはよろしくありません。

セレクタ:https://material.angular.io/components/select/overview

調べてみるとng-selectというものが存在しますが、これはこれでAngular Materialとの使い合わせがよくありません。(主にmat-form-fieldと)

そこでセレクタのフィルタとして使えるオートコンプリートをAngular Materialを用いて作成してみました!

ReactのMUIにあるAutocompleteを参考に作成しました。

完成内容

実装

HTML

<form>
  <mat-form-field appearance="fill">
    <mat-label>ラベル</mat-label>
    <input
      type="text"
      matInput
      #InputField="matInput"
      [formControl]="myControl"
      [matAutocomplete]="auto"
    />
    <mat-icon matSuffix
      >{{ InputField.focused ? 'expand_less' : 'expand_more' }}</mat-icon
    >
    <mat-autocomplete
      autoActiveFirstOption
      #auto="matAutocomplete"
      [displayWith]="autoCompleteDisplayFn"
      (closed)="onCloseAutoCompleteOptions()"
    >
      <mat-option
        *ngFor="let option of filteredOptions | async"
        [value]="option"
        [ngClass]="_allowSelection(option.id)"
      >
        {{option.value}}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

css

.no-data-auto-complete {
  pointer-events: none;
  color: gray;
}

TypeScript

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

interface ItemType {
  id: number;
  value: string;
}

@Component({
  selector: 'autocomplete-auto-active-first-option-example',
  templateUrl: 'autocomplete-auto-active-first-option-example.html',
  styleUrls: ['autocomplete-auto-active-first-option-example.css'],
})
export class AutocompleteAutoActiveFirstOptionExample implements OnInit {
  myControl = new FormControl<string | ItemType>('');
  options: ItemType[] = [
    { id: 1, value: 'aaa' },
    { id: 2, value: 'bbb' },
    { id: 3, value: 'ccc' },
  ];
  filteredOptions: Observable<ItemType[]> = of([]);

  ngOnInit() {
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(''),
      filter((x): x is string | ItemType => x != null),
      map((x: string | ItemType) => (typeof x === 'string' ? x : x.value)),
      map((x: string) => {
        if (x === '') {
          return [...this.options];
        }

        // 部分一致
        const filteredItemList = this.options.filter((i) =>
          i.value.includes(x)
        );
        return filteredItemList.length > 0
          ? filteredItemList
          : [{ id: -1, value: 'not found' }];
      })
    );
  }

  autoCompleteDisplayFn(item?: ItemType | null): string {
    return item ? item.value : '';
  }

  onCloseAutoCompleteOptions(): void {
    if (typeof this.myControl.value === 'string') {
      this.myControl.setValue('');
    }
  }

  _allowSelection(id: number): { [className: string]: boolean } {
    return {
      'no-data-auto-complete': id === -1,
    };
  }
}
  • 何も見つからなかったときは id = -1 を入れています
  • 今回は入力内容の部分一致で選択肢を絞っています
  • startWith('') を入れることで、仮に最初から値が入っていたとしても、最初に限りすべての選択肢の中から選べるようにしてあります
  • あくまで選択肢のためのフィルタであって欲しいため、選択肢と完全一致している文字列が入力されていても選択肢をクリックしなければ、選択していないものとして扱います
  • なにも値が存在しないときに not found を入れることで closed が発火するようにしています(選択肢がないとフォームから関心が外れても選択肢が畳まれないため closed は発火しない)

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0