仅对第一个动态控件执行的Angular 9 Formarray搜索操作 [英] Angular 9 Formarray search operation executing for only first dynamic control
问题描述
我有一个带有某些字段的formarray,其中之一是输入字段.在此字段中,根据用户输入,我们将搜索API并返回一些数据.
I have a formarray with certain fields one of which is input. In this field, on user input, we search in the API's and return some data.
问题是,它仅适用于首次动态控制.当我添加更多控件时,它对它们不起作用.
The problem is, it is working only for first dynamic control. When I add more controls, it is not working for them.
我想这是因为我已经在ngAfterViewInit()
中编写了搜索逻辑.
I guess this is happening because I have written search logic in ngAfterViewInit()
.
但是那有什么选择呢?
我不知道如何解决这个问题.
I can't get how to solve this problem.
先谢谢您
.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仅获得第一个元素.
if you use @ViewChild
, viewChild only get the first element.
如果您使用@ViewChildren,则需要为QueryList的每个元素创建这么多事件
if you're using @ViewChildren you need get create so many event for each Element of the QueryList
@ViewChildren('input') inputs:QueryList<ElementRef>
this.inputs.forEach(input=>{
fromEvent(input.nativeElement,'keyup')
})
无论如何,如果数组最初是固定元素,则此方法不起作用.好吧,您可以订阅input.changes和bla-bla-bla
Anyway this NOT work -works if the array was fixed elements at first. Well, you can subscribe to inputs.changes and bla-bla-bla
不使用fromEvents方法.这个想法来自此Amir Tugendhaft的入门博客.第一个不是使用"productList",而是可观察到的productList和异步管道.因为我们有几个"productList",所以我们需要一个可观察的数组
The way is NOT use fromEvents. The idea goes from this Amir Tugendhaft's entry blog. The first is not use a "productList" else an observable of productList and async pipe. As we has severals "productList, we need an array of observables
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">
并遍历结果".
See how we use <ng-container *ngIf="product_list$[i] |async as results">
and iterate over "results".
好吧,下一步是更改函数addProductGroup以创建可观察的对象并将其赋值为数组productList $
Well, the next step is change the function addProductGroup to create the observable and asing to the array productList$
方法是订阅控件的valueChanges,但使用switchMap返回服务的响应
The way is subscribe to valueChanges of the control, but return the response of the service using 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"时要格外小心,
At last, be carefull when call to addGroup to send as argument the "index", so, at first
this.purchaseform = this.fb.group({
...
product: this.fb.array([this.addProductGroup(0)]) //<--see the "0"
});
还有
addproduct() {
this.product.push(this.addProductGroup(this.product.length));
}
您可以看到 stackblitz (我使用of来模拟服务,显然您要调用API)
You can see the stackblitz (I simulate the service using of, obviously you call to an API)
NOTE: If you want use ngbTypeHead see this post
更新使用[displayWith]="displayFn"
我在mat-option中写了
I wrote in mat-option
<mat-option *ngFor="let state of results " [value]="state.state">
这使值是属性状态",如果我们需要整个对象,则需要编写
This make that the value is the property "state", if we want the whole object we need write
<mat-option *ngFor="let state of results" [value]="state">
但是我们还需要进行一些更改,首先是将其添加到垫子中.使用
but also we need make a little change, the first is add in the matAutocomplete the displaywith
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn" >
我们的功能可以是例如
displayFn(data: any): string {
return data ?data.name+'-'+data.state : '';
}
第二个是当我们收到一个对象或一个字符串时,在考虑服务的情况下更改一些"search_Products".我们可以将功能替换为
The second is change a few the "search_Products" in the service taking account when we received an object or an string. We can replace the function with some like
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}))
}))
}
I forked the stackblitz with this changes
这篇关于仅对第一个动态控件执行的Angular 9 Formarray搜索操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!