|||||||||||||||||||||

なんぶ電子

- 更新: 

Angular Materialのオートコンプリート

Angular Material Auto Complete

前回、Angular Materialのテーブルを使ってデータを表示してみました。今回は、そこで取得したテーブルデータに基づいて、オートコンプリートで候補を出せるように入力欄を作成してみます。

app.module.tsの設定

まずAppModule.tsにオートコンプリート用のAngular Materialのモジュール「MatAutocompleteModule」を設定します。また、Angular本体のリアクティブフォームも利用することになるので、「ReactiveFormsModule」もインポートします。

app.module.ts

...
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    ...
    MatAutocompleteModule,
    ReactiveFormsModule
  ],
...

app.component.tsの設定

.tsファイルでは、リアクティブフォームで使うFormControlと、非同期処理ライブラリのObservableまわりで必要なものをimportしています。

定義をする際必須ではないですが、Observable値である変数にはそれとわかるように末尾に$を付ける慣例があるそうです。

全体としては次のような感じになります。filterという言葉がでてきますが、先ほどのテーブルのフィルタとはまったく別ものです。

app.component.ts

import { Component,OnInit, ViewChild } from '@angular/core';
import { MatTable, MatTableDataSource, } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { FormControl } from '@angular/forms';
import { map, Observable, startWith } from 'rxjs';

export interface RowData {
  area_code:string,
  area_name:string,
  price: number
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  
  title='test-app';
  
  public fCont = new FormControl('');
  public obCityOptions$:Observable<string[]> | undefined;
  public strAllCities:string[]=[];

  columnsdefine: string[] = ['col_area_code','col_area_name','col_price'];
 
  public tableData= new MatTableDataSource<RowData>();
  
  @ViewChild(MatTable) table: MatTable<RowData> | undefined;
  @ViewChild(MatPaginator) paginator: MatPaginator | undefined;

  ngAfterViewInit() {
    if (this.paginator) {
      this.tableData.paginator = this.paginator;
    }
  }
  ngOnInit(): void {

    this.tableData=new MatTableDataSource([
      {area_code:"01100",area_name:"札幌市",price:177},
      {area_code:"01202",area_name:"函館市",price:175},
      {area_code:"01204",area_name:"旭川市",price:176},
      ...
    ]);

    this.setTextFilter();
  }

  private setTextFilter():void {
    
    this.strAllCities=[];

    for(let i = 0; i < this.tableData.data.length; i++) {
      this.strAllCities.push(this.tableData.data[i].area_name);
    }

    this.obCityOptions$ = this.fCont.valueChanges.pipe(
      startWith(''),
      map(value => this.textfilter(value)),
    );
  }

  private textfilter(value: string): string[] {
    return this.strAllCities.filter(option => option.includes(value));
  }
}

簡単に説明すると、全都市の名前をいったんstrAllCitiesに保存しておいて、HTMLのフォームから流れてくる値で候補を見つけて、Observableにセットするという流れになります。

app.component.htmlの設定

HTMLでは変化するObservableから値を受け取り*ngForでループさせるのですが、asyncパイプを利用する必要があります。非同期処理で*ngForを使う際には| asyncとする必要があります。この時indexを使いたい場合はパイプの後に記述します。

ここでObservableとasyncパイプを利用しているのは、テキストボックスの値の変更をObservableを使って検知しているからです。そのようなことをしない場合、たとえば事前に取得済みの過去の入力履歴から候補をだすというような時は、Observableやasyncパイプは不要です。optionを列挙する箇所を単なる*ngForのループや固定値にしてもオートコンプリート自体は問題なく稼働します。

app.component.html

<div class="wrapper">

<input matInput [formControl]="fCont" [matAutocomplete]="auto"/>
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
  <mat-option *ngFor="let opt of obCityOptions$ | async; let i = index;" [value]="opt">
    {{i+opt}}
  </mat-option>
</mat-autocomplete>

<table mat-table [dataSource]="tableData">

  <ng-container matColumnDef="col_area_code">
    <th mat-header-cell *matHeaderCellDef> 地域コード </th>
    <td mat-cell *matCellDef="let element"> {{element.area_code}} </td>
  </ng-container>

  <ng-container matColumnDef="col_area_name">
    <th mat-header-cell *matHeaderCellDef> 地域 </th>
    <td mat-cell *matCellDef="let element"> {{element.area_name}} </td>
  </ng-container>

  <ng-container matColumnDef="col_price">
    <th mat-header-cell *matHeaderCellDef> 価格 </th>
    <td mat-cell *matCellDef="let element"> {{element.price}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="columnsdefine"></tr>
  <tr mat-row *matRowDef="let row; columns: columnsdefine;"></tr>

</table>
<mat-paginator [pageSizeOptions]="[5, 10, 15]" showFirstLastButtons>
</mat-paginator>
</div>

HTMLではmatInputのmatAutoComplete属性に、後から設定するテンプレート変数autoをバインドします。これはHTML要素ではなくmatAutocomplete要素として渡す必要があります(#auto="matAutocomplete"の部分)。

obCityOptions$が文字列配列を流すObservableオブジェクトなので、それをanyncパイプを利用して*ngForでループさせることで、オートコンプリートの候補値を作成します。

必要に応じて.ts(コンポーネント)側のpipeで「debounceTime(ミリ秒)」として入力を少し待ち受けて値を流したり、mapの部分を「switchMap」に変えて複数の処理が流れてきた場合に前の処理をキャンセルしたりもできます。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告