Angular formArray 内の autocomplete を filter する
はじめに
可変長の Angular formArray 内で autocomplete の要素を filter する方法をまとめます。
formArray が可変長の場合 filter を当てる autocomplete 要素の位置を特定する必要があるので少し工夫が必要でした。
サンプルアプリケーションのソースコードを Github で公開しています。
アプリケーションはこちら
Angular formArray 内の autocomplete を filter する
mat-option
を *ngFor="let product of filteredOptions[index] | async"
のように filteredOptions
が formArray の何番目であるかを認識させています。
<div *ngFor="let c of products.controls; let index=index; let last=last;" [formGroupName]="index"> <mat-form-field> <input #stateInput matInput type="text" placeholder="Product" aria-label="Number" formControlName="product" [matAutocomplete]="auto"> <button mat-button *ngIf="stateInput.value" matSuffix mat-icon-button aria-label="Clear" (click)="delSelectedProduct(index)"> <mat-icon>close</mat-icon> </button> <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"> <mat-option (onSelectionChange)="stateInput.value != undefined" *ngFor="let product of filteredOptions[index] | async" [value]="product"> {{product.productName}} </mat-option> </mat-autocomplete> </mat-form-field> </div>
filteredOptions
は Observable<Product[]>[] = [];
のように定義しています。
managedFilter
というメソッドで formArray の index を受け取れるようにしておき入力された文字列を Product.productName に対して filter を試みます。
_filter
というメソッドで実際に filter を実施しますが、Angular material の autocomplete で扱いやすいように Product[]
のような Interface の配列として return するようにします。
template との紐付けは 要素が足される addInput()
メソッドの中で this.managedFilter(this.products.length - 1);
のようにしているのと、初期化時に this.managedFilter(0);
としています。ここはなんかあまりかっこよくないですね。どうすれば良いのか。。
export interface Product { productName: string; productCode: string; price: number; maxQuantity: number; } ~~ 省略 ~~ filteredOptions: Observable<Product[]>[] = []; ngOnInit() { ~~ 省略 ~~ this.managedFilter(0); } managedFilter(index: number) { const arrayControl = this.productFormGroup.get('products') as FormArray; this.filteredOptions[index] = arrayControl.at(index).get('product').valueChanges .pipe( startWith<string | Product>(''), map(value => typeof value === 'string' ? value : value.productName), map(productName => productName ? this._filter(productName) : this.sampleProducts.slice()) ); } private _filter(value: string): Product[] { const filterValue = value.toLowerCase(); return this.sampleProducts.filter(option => option.productName.toLowerCase().includes(filterValue)); } addInput() { this.products.push(this.formBuilder.group({ product: this.formBuilder.control('', []), productNumber: this.formBuilder.control(1, [Validators.min(1), CustomValidator.integer]), }, { validators: CustomValidator.maxQuantity })); this.managedFilter(this.products.length - 1); }
以上でAngular formArray 内の autocomplete を filter することができます がなんとか動いている状態を作り出せます。
まとめ
Angular formArray 内の autocomplete を filter する方法をまとめました。
サンプルアプリケーションのソースコードを Github で公開しています。