Angular 9 只为第一个动态控制执行的 Formarray 搜索操作
Angular 9 Formarray search operation executing for only first dynamic control
我有一个包含某些字段的格式数组,其中一个是输入字段。在此字段中,根据用户输入,我们在 API 和 return 中搜索一些数据。
问题是,它只对第一个动态控制有效。当我添加更多控件时,它对它们不起作用。
我猜这是因为我在 ngAfterViewInit()
.
中编写了搜索逻辑
但是还有什么选择呢。
我不知道如何解决这个问题。
提前致谢
.ts
purchaseform = this.fb.group({
vendor_mobile : ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern("^[0-9]*$")]],
product : this.fb.array([this.addProductGroup()])
})
addProductGroup() {
return this.fb.group({
product_name : ['', Validators.required ],
product_quantity : ['', [Validators.required, Validators.pattern("^[0-9]*$") ]],
product_Buyingprice : ['', [ Validators.required, Validators.pattern("^[0-9]*$") ]],
})
}
get product() {
return this.purchaseform.get('product') as FormArray;
}
addproduct() {
this.product.push(this.addProductGroup())
}
remove_product(index) {
return this.product.removeAt(index)
}
ngAfterViewInit() {
// server-side search
fromEvent(this.input.nativeElement,'keyup')
.pipe(
filter(Boolean),
debounceTime(500),
distinctUntilChanged(),
tap((event:KeyboardEvent) => {
console.log(event)
console.log(this.input.nativeElement.value)
this.productService.search_Products(this.input.nativeElement.value).subscribe(data =>{
if(data){
this.product_list = data
console.log(this.product_list)
}
})
})
)
.subscribe();
}
.html
<form [formGroup]="purchaseform">
// other fields
<div formArrayName = "product" *ngFor="let prod of product?.controls; let i = index">
<ng-container [formGroupName]="i">
<mat-form-field class="example-full-width">
<mat-label>Enter product name</mat-label>
<input matInput #input
aria-label="product name"
[matAutocomplete]="auto"
formControlName ="product_name">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
<mat-option *ngFor="let state of product_list " [value]="state ">
<span>{{state.name}}</span>
</mat-option>
<mat-option *ngIf="!product_list || !product_list.length" class="text-danger">
Such product does not exists
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label>Enter product quantity</mat-label>
<input matInput formControlName="product_quantity" type="number" >
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label>Enter product price</mat-label>
<input matInput formControlName="product_Buyingprice" type="number">
</mat-form-field>
<button type="button" [disabled]="!purchaseform.valid" class="btn btn-primary" (click) = "addproduct()">Add product</button>
<button [disabled] = "i==0" type="button" class="btn btn-danger" (click) = "remove_product(i)">Delete product</button>
</ng-container>
</div>
</form>
如果使用@ViewChild
,viewChild只获取第一个元素。
如果您使用@ViewChildren,您需要为 QueryList 的每个元素创建这么多事件
@ViewChildren('input') inputs:QueryList<ElementRef>
this.inputs.forEach(input=>{
fromEvent(input.nativeElement,'keyup')
})
无论如何,如果数组最初是固定元素,这将不起作用。嗯,你可以订阅 inputs.changes 和 bla-bla-bla
方法是不使用 fromEvents。这个想法来自 this Amir Tugendhaft's entry blog。第一个是不使用 "productList" else productList 和异步管道的可观察对象。由于我们有几个“productList,我们需要一个可观察数组
productList$:Observable<any>[]=[];
而 .html 会像
<div formArrayName = "product" *ngFor="let prod of product?.controls; let i = index">
<ng-container [formGroupName]="i">
<mat-form-field class="example-full-width">
<mat-label>Enter product name</mat-label>
<input matInput #input
aria-label="product name"
[matAutocomplete]="auto"
formControlName ="product_name">
<mat-autocomplete #auto="matAutocomplete">
<ng-container *ngIf="product_list$[i] |async as results">
<mat-option *ngFor="let state of results " [value]="state.state">
<span>{{state.name}}</span>
</mat-option>
<mat-option *ngIf="prod.get('product_name').value &&
results?.length<=0" class="text-danger">
Such product does not exists
</mat-option>
<ng-container>
</mat-autocomplete>
</mat-form-field>
</ng-container>
</div>
看看我们如何使用 <ng-container *ngIf="product_list$[i] |async as results">
并迭代 "results"。
好吧,下一步是更改函数 addProductGroup 以创建可观察到的数组 productList$
方式是订阅控件的valueChanges,但是return服务的响应使用switchMap
addProductGroup(index) {
//we use an auxiliar const
const group = this.fb.group({
product_name: ["", Validators.required],
product_quantity: [
"",
[Validators.required, Validators.pattern("^[0-9]*$")]
],
product_Buyingprice: [
"",
[Validators.required, Validators.pattern("^[0-9]*$")]
]
});
//See how the observables will be valueChange of the "product_name"
//of this formGroup, using a pipe and switchMap
this.productList$[index] = group.get("product_name").valueChanges.pipe(
debounceTime(300),
switchMap(value => this.productService.search_Products(value))
);
//finally, we return the group
return group;
}
最后,当调用 addGroup 将 "index" 作为参数发送时要小心,所以,首先
this.purchaseform = this.fb.group({
...
product: this.fb.array([this.addProductGroup(0)]) //<--see the "0"
});
和
addproduct() {
this.product.push(this.addProductGroup(this.product.length));
}
可以看到the stackblitz(我模拟了服务的使用,明明你调用的是一个API)
注意:如果您想使用 ngbTypeHead,请参阅
更新使用[displayWith]="displayFn"
我在mat-option里写了
<mat-option *ngFor="let state of results " [value]="state.state">
这使得值是 属性 "state",如果我们想要整个对象,我们需要写
<mat-option *ngFor="let state of results" [value]="state">
但我们还需要做一点改变,首先是在 matAutocomplete 中添加 displaywith
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
我们的函数可以像这样,例如
displayFn(data: any): string {
return data ?data.name+'-'+data.state : '';
}
第二个是更改服务中的一些 "search_Products" 当我们收到一个对象或一个字符串时考虑。我们可以用
之类的替换函数
search_Products(name: any): Observable<any> {
//name can be a string or an object, so
name=name.toLowerCase?name.toLowerCase():name.name
//now name is a string and can filter or call to the appi
return of(states.filter(x=>x && x.toLowerCase().indexOf(name)>=0)).pipe(map(result=>
{
return result.map(x=>({state:x,name:x}))
}))
}
我forked the stackblitz有了这个变化
我有一个包含某些字段的格式数组,其中一个是输入字段。在此字段中,根据用户输入,我们在 API 和 return 中搜索一些数据。
问题是,它只对第一个动态控制有效。当我添加更多控件时,它对它们不起作用。
我猜这是因为我在 ngAfterViewInit()
.
但是还有什么选择呢。
我不知道如何解决这个问题。
提前致谢
.ts
purchaseform = this.fb.group({
vendor_mobile : ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern("^[0-9]*$")]],
product : this.fb.array([this.addProductGroup()])
})
addProductGroup() {
return this.fb.group({
product_name : ['', Validators.required ],
product_quantity : ['', [Validators.required, Validators.pattern("^[0-9]*$") ]],
product_Buyingprice : ['', [ Validators.required, Validators.pattern("^[0-9]*$") ]],
})
}
get product() {
return this.purchaseform.get('product') as FormArray;
}
addproduct() {
this.product.push(this.addProductGroup())
}
remove_product(index) {
return this.product.removeAt(index)
}
ngAfterViewInit() {
// server-side search
fromEvent(this.input.nativeElement,'keyup')
.pipe(
filter(Boolean),
debounceTime(500),
distinctUntilChanged(),
tap((event:KeyboardEvent) => {
console.log(event)
console.log(this.input.nativeElement.value)
this.productService.search_Products(this.input.nativeElement.value).subscribe(data =>{
if(data){
this.product_list = data
console.log(this.product_list)
}
})
})
)
.subscribe();
}
.html
<form [formGroup]="purchaseform">
// other fields
<div formArrayName = "product" *ngFor="let prod of product?.controls; let i = index">
<ng-container [formGroupName]="i">
<mat-form-field class="example-full-width">
<mat-label>Enter product name</mat-label>
<input matInput #input
aria-label="product name"
[matAutocomplete]="auto"
formControlName ="product_name">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
<mat-option *ngFor="let state of product_list " [value]="state ">
<span>{{state.name}}</span>
</mat-option>
<mat-option *ngIf="!product_list || !product_list.length" class="text-danger">
Such product does not exists
</mat-option>
</mat-autocomplete>
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label>Enter product quantity</mat-label>
<input matInput formControlName="product_quantity" type="number" >
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label>Enter product price</mat-label>
<input matInput formControlName="product_Buyingprice" type="number">
</mat-form-field>
<button type="button" [disabled]="!purchaseform.valid" class="btn btn-primary" (click) = "addproduct()">Add product</button>
<button [disabled] = "i==0" type="button" class="btn btn-danger" (click) = "remove_product(i)">Delete product</button>
</ng-container>
</div>
</form>
如果使用@ViewChild
,viewChild只获取第一个元素。
如果您使用@ViewChildren,您需要为 QueryList 的每个元素创建这么多事件
@ViewChildren('input') inputs:QueryList<ElementRef>
this.inputs.forEach(input=>{
fromEvent(input.nativeElement,'keyup')
})
无论如何,如果数组最初是固定元素,这将不起作用。嗯,你可以订阅 inputs.changes 和 bla-bla-bla
方法是不使用 fromEvents。这个想法来自 this Amir Tugendhaft's entry blog。第一个是不使用 "productList" else productList 和异步管道的可观察对象。由于我们有几个“productList,我们需要一个可观察数组
productList$:Observable<any>[]=[];
而 .html 会像
<div formArrayName = "product" *ngFor="let prod of product?.controls; let i = index">
<ng-container [formGroupName]="i">
<mat-form-field class="example-full-width">
<mat-label>Enter product name</mat-label>
<input matInput #input
aria-label="product name"
[matAutocomplete]="auto"
formControlName ="product_name">
<mat-autocomplete #auto="matAutocomplete">
<ng-container *ngIf="product_list$[i] |async as results">
<mat-option *ngFor="let state of results " [value]="state.state">
<span>{{state.name}}</span>
</mat-option>
<mat-option *ngIf="prod.get('product_name').value &&
results?.length<=0" class="text-danger">
Such product does not exists
</mat-option>
<ng-container>
</mat-autocomplete>
</mat-form-field>
</ng-container>
</div>
看看我们如何使用 <ng-container *ngIf="product_list$[i] |async as results">
并迭代 "results"。
好吧,下一步是更改函数 addProductGroup 以创建可观察到的数组 productList$
方式是订阅控件的valueChanges,但是return服务的响应使用switchMap
addProductGroup(index) {
//we use an auxiliar const
const group = this.fb.group({
product_name: ["", Validators.required],
product_quantity: [
"",
[Validators.required, Validators.pattern("^[0-9]*$")]
],
product_Buyingprice: [
"",
[Validators.required, Validators.pattern("^[0-9]*$")]
]
});
//See how the observables will be valueChange of the "product_name"
//of this formGroup, using a pipe and switchMap
this.productList$[index] = group.get("product_name").valueChanges.pipe(
debounceTime(300),
switchMap(value => this.productService.search_Products(value))
);
//finally, we return the group
return group;
}
最后,当调用 addGroup 将 "index" 作为参数发送时要小心,所以,首先
this.purchaseform = this.fb.group({
...
product: this.fb.array([this.addProductGroup(0)]) //<--see the "0"
});
和
addproduct() {
this.product.push(this.addProductGroup(this.product.length));
}
可以看到the stackblitz(我模拟了服务的使用,明明你调用的是一个API)
注意:如果您想使用 ngbTypeHead,请参阅
更新使用[displayWith]="displayFn"
我在mat-option里写了
<mat-option *ngFor="let state of results " [value]="state.state">
这使得值是 属性 "state",如果我们想要整个对象,我们需要写
<mat-option *ngFor="let state of results" [value]="state">
但我们还需要做一点改变,首先是在 matAutocomplete 中添加 displaywith
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
我们的函数可以像这样,例如
displayFn(data: any): string {
return data ?data.name+'-'+data.state : '';
}
第二个是更改服务中的一些 "search_Products" 当我们收到一个对象或一个字符串时考虑。我们可以用
之类的替换函数search_Products(name: any): Observable<any> {
//name can be a string or an object, so
name=name.toLowerCase?name.toLowerCase():name.name
//now name is a string and can filter or call to the appi
return of(states.filter(x=>x && x.toLowerCase().indexOf(name)>=0)).pipe(map(result=>
{
return result.map(x=>({state:x,name:x}))
}))
}
我forked the stackblitz有了这个变化