Angular动态添加输入框的Autocomplete搜索筛选失效求助
问题分析与解决方案
核心问题原因
- 共享Observable实例:所有自动完成组件绑定到同一个
filteroptionsObservable,每次添加新字段时该Observable会被覆盖,仅最后一个字段的控件能驱动它。 - 冲突的表单控件绑定:输入框同时使用
formControlName和[formControl]属性,Angular无法同时处理这两种绑定,导致控件状态异常。
分步修复
1. 重构组件代码,为每个字段创建独立的Observable
- 替换单个
filteroptions变量为数组,存储每个产品字段对应的自动完成筛选Observable。 - 移除无用的
formcontrol变量。 - 添加产品时将新的Observable推入数组,删除产品时同步移除对应项。
修改后的TypeScript关键代码:
// 替换原filteroptions变量 filterOptionsList: Observable<string[]>[] = []; // 移除formcontrol = new FormControl(''); addNewProduct() { this.items = this.productFormarray.get('productdetails') as FormArray; const newProduct = this.createNewProduct(); this.items.push(newProduct); const indexvalue = this.items.length - 1; this.productFormarray.get('productdetails').controls[indexvalue].get('quantit').setValue(this.quantitylist[1]); const productNameControl = newProduct.get('productname'); if (productNameControl) { // 创建当前字段的筛选Observable并推入数组 const filterOptions = productNameControl.valueChanges.pipe( startWith(''), map(value => this._filter(value)) ); this.filterOptionsList.push(filterOptions); productNameControl.valueChanges.subscribe(selectedProductName => { this.onProductChange(selectedProductName, indexvalue); }); } // 其余quantity和price的订阅代码保持不变 } removeProduct(index: any) { this.items = this.productFormarray.get('productdetails') as FormArray; this.items.removeAt(index); // 同步移除对应位置的筛选Observable this.filterOptionsList.splice(index, 1); }
2. 修复HTML中的绑定问题
- 移除输入框上的
[formControl]="formcontrol"属性,保留formControlName="productname"即可。 - 将自动完成的数据源改为对应索引的Observable。
修改后的HTML关键部分:
<tr *ngFor="let product of productdetailsarray.controls; let i=index" [formGroupName]="i"> <td> <div class="formGroup"> <input formControlName="productname" matInput type="text" [matAutocomplete]="auto" class="form-control" /> <mat-autocomplete #auto="matAutocomplete"> <mat-option *ngFor="let product of filterOptionsList[i] | async" [value]="product"> {{product}} </mat-option> </mat-autocomplete> </div> </td> <!-- 其余单元格代码保持不变 --> </tr>
完整修正代码
HTML代码
<form [formGroup]="productFormarray" (ngSubmit)="onSubmit()"> <div class="reg-right"> <div class="formGroup"> <label for="customername" class="form-label">Customer Name</label> <input type="text" class="form-control" id="customername" placeholder="Customer Name" formControlName="customername"> </div> <div class="formGroup" formArrayName="productdetails"> <div class="table-responsive"> <table class="table table-bordered" style="margin-top: 20px;"> <thead> <tr> <td style="width:40%"> Product Name </td> <td style="width:15%"> Quantity </td> <td style="width:15%"> Price </td> <td style="width:15%"> Gst </td> <td> </td> </tr> </thead> <tr *ngFor="let product of productdetailsarray.controls; let i=index" [formGroupName]="i"> <td> <div class="formGroup"> <input formControlName="productname" matInput type="text" [matAutocomplete]="auto" class="form-control" /> <mat-autocomplete #auto="matAutocomplete"> <mat-option *ngFor="let product of filterOptionsList[i] | async" [value]="product"> {{product}} </mat-option> </mat-autocomplete> </div> </td> <td> <div class="formGroup"> <select class="form-control" id="quantit" formControlName="quantit" name="quantit"> <option *ngFor="let quantity of quantitylist" [ngValue]="quantity"> {{quantity}} </option> </select> </div> </td> <td> <div class="formGroup"> <input type="text" class="form-control" id="price" formControlName="price" placeholder="Price " readonly name="price"> </div> </td> <td> <div class="formGroup"> <input type="text" class="form-control" id="gst" formControlName="gst" placeholder="Gst" name="gst" readonly> </div> </td> <td> <a type="button" class="form-control btn btn-primary" style="background-color: red;" (click)="removeProduct(i)">Remove (-)</a> </td> </tr> </table> </div> <a type="button" class="btn btn-secondary" style="background-color: green;" (click)="addNewProduct()">Add(+)</a> <br /> </div> <br /> <br /> <div class="row"> <div class="col-md-6"> </div> <div class="col-md-6"> <div class="formGroup"> <label for="totalprice" class="form-label" style="margin-top: 10pt;">Total Product Price</label> <input type="text" class="form-control form-control1" id="totalprice" formControlName="totalprice" placeholder="totalprice" name="totalprice" style="margin-left: 20pt; float:right" readonly> </div> </div> </div> <button type="submit" class="btn btn-primary">Submit</button> </div> </form>
TypeScript代码
import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormGroup, FormControl, FormArray, FormBuilder, Validators } from '@angular/forms' import { Observable, map, startWith, Subscription } from 'rxjs'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.component.css'] }) export class TestComponent implements OnInit, OnDestroy { productFormarray: any; quantitylist = [0.5, 1, 1.5]; items!: FormArray; totalGstPrice: number = 0; totalProductPrice: number = 0; productlist = [{ productname: "apple", price: 10, gst: 10 }, { productname: "orange", price: 20, gst: 12 }, { productname: "lemon", price: 30, gst: 20 }]; productlistss = ['apple', 'lemon', 'orange']; filterOptionsList: Observable<string[]>[] = []; // 存储订阅实例以防止内存泄漏 private subscriptions: Subscription[] = []; constructor(private fb: FormBuilder) { this.productFormarray = new FormGroup({ customername: new FormControl('', Validators.required), productdetails: new FormArray([]), remember: new FormControl('true'), totalprice: new FormControl(''), }) } private _filter(value: string): string[] { const searchvalue = value.toLocaleLowerCase(); return this.productlistss.filter(option => option.toLocaleLowerCase().includes(searchvalue)); } onProductChange(selectedProductName: string, index: number) { const selectedProduct = this.productlist.find( (product) => product.productname === selectedProductName ); if (selectedProduct) { const productDetailsArray = this.productFormarray.get( 'productdetails' ) as FormArray; if (productDetailsArray && productDetailsArray.at(index)) { const quantityControl = productDetailsArray.at(index).get('quantit'); if (quantityControl) { const quantity = quantityControl.value; const price = selectedProduct.price * quantity; const priceControl = productDetailsArray.at(index).get('price'); if (priceControl) { priceControl.setValue(price); } } } } } onQuantityChange(selectedQuantity: number, index: number) { const productDetailsArray = this.productFormarray.get( 'productdetails' ) as FormArray; if (productDetailsArray && productDetailsArray.at(index)) { const productNameControl = productDetailsArray.at(index).get('productname'); if (productNameControl) { const selectedProductName = productNameControl.value; const selectedProduct = this.productlist.find( (product) => product.productname === selectedProductName ); if (selectedProduct) { const price = selectedProduct.price * selectedQuantity; const priceControl = productDetailsArray.at(index).get('price'); if (priceControl) { priceControl.setValue(price); } } } } } onPriceChange(selectedQuantity: number, index: number) { const productDetailsArray = this.productFormarray.get('productdetails') as FormArray; if (productDetailsArray && productDetailsArray.at(index)) { const productNameControl = productDetailsArray.at(index).get('productname'); if (productNameControl) { const selectedProductName = productNameControl.value; const selectedProduct = this.productlist.find( (product) => product.productname === selectedProductName ); if (selectedProduct) { const priceControl = productDetailsArray.at(index).get('price'); const gst = ((selectedProduct.gst * priceControl?.value) / 100); const gstControl = productDetailsArray.at(index).get('gst'); gstControl?.setValue(gst); } } } } addNewProduct() { this.items = this.productFormarray.get('productdetails') as FormArray; const newProduct = this.createNewProduct(); this.items.push(newProduct); const indexvalue = this.items.length - 1; this.productFormarray.get('productdetails').controls[indexvalue].get('quantit').setValue(this.quantitylist[1]); const productNameControl = newProduct.get('productname'); if (productNameControl) { const filterOptions = productNameControl.valueChanges.pipe( startWith(''), map(value => this._filter(value)) ); this.filterOptionsList.push(filterOptions); const sub = productNameControl.valueChanges.subscribe(selectedProductName => { this.onProductChange(selectedProductName, indexvalue); }); this.subscriptions.push(sub); } const quantityControl = newProduct.get('quantit'); if (quantityControl) { const sub = quantityControl.valueChanges.subscribe(selectedQuantity => { this.onQuantityChange(selectedQuantity, indexvalue); }); this.subscriptions.push(sub); } const priceControl = newProduct.get('price'); if (priceControl) { const sub = priceControl.valueChanges.subscribe((selectedProductName) => this.onPriceChange(selectedProductName, indexvalue) ); this.subscriptions.push(sub); } } createNewProduct(): FormGroup { return new FormGroup({ productname: new FormControl('', Validators.required), quantit: new FormControl('1', Validators.required), price: new FormControl('', Validators.required), gst: new FormControl('', Validators.required) }) } removeProduct(index: any) { this.items = this.productFormarray.get('productdetails') as FormArray; this.items.removeAt(index); this.filterOptionsList.splice(index, 1); } get productdetailsarray() { return this.productFormarray.get('productdetails') as FormArray; } ngOnInit() { this.addNewProduct(); } ngOnDestroy() { // 清理所有订阅防止内存泄漏 this.subscriptions.forEach(sub => sub.unsubscribe()); } onSubmit() { console.log(this.productFormarray.value); } }
额外优化说明
- 添加了
OnDestroy接口和订阅管理,防止内存泄漏。 - 移除了HTML中重复的
class="formGroup"属性。 - 保留了原有的业务逻辑,仅修复自动完成功能的问题。
内容的提问来源于stack exchange,提问作者Reegan
相关产品推荐
相关产品推荐

