Angular Materialのオートコンプリート
前回、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」に変えて複数の処理が流れてきた場合に前の処理をキャンセルしたりもできます。