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.
I guess this is happening because I have written search logic in ngAfterViewInit().
But what's the alternative then.
I can't get how to solve this problem.
Thank you In advance
.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>
if you use
@ViewChild, viewChild only get the first element.if you're using @ViewChildren you need get create so many event for each Element of the QueryList
Anyway this NOT work -works if the array was fixed elements at first. Well, you can subscribe to inputs.changes and bla-bla-bla
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
And the .html will be like
See how we use
<ng-container *ngIf="product_list$[i] |async as results">and iterate over "results".Well, the next step is change the function addProductGroup to create the observable and asing to the array productList$
The way is subscribe to valueChanges of the control, but return the response of the service using switchMap
At last, be carefull when call to addGroup to send as argument the "index", so, at first
And
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
Update use of
[displayWith]="displayFn"I wrote in mat-option
This make that the value is the property "state", if we want the whole object we need write
but also we need make a little change, the first is add in the matAutocomplete the displaywith
And our function can be like, e.g.
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
I forked the stackblitz with this changes