I am working on billing module, which have input fields that are added dynamically. Here I am using autocomplete search filter for dynamically added input fields (Productname).
But the autocomplete search is working fine, if there is one productname field. When I add more than one productName details, only the lastly added field is working correctly. When I try to change the previous productname field it is not working.
Below html code
<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" 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" [formControl]="formcontrol" />
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let product of filteroptions | 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>
Below typescriptcode
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormGroup, FormControl, FormArray, FormBuilder, NgForm, Validators } from '@angular/forms'
import { EMPTY, Observable, map, of, startWith } from 'rxjs';
import { toArray } from 'rxjs';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent implements OnInit
{
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'];
filteroptions!: Observable<string[]>;
formcontrol = new FormControl('');
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) {
this.filteroptions = productNameControl.valueChanges.pipe(startWith(''), map(value => this._filter(value)));
console.log('filteroption--- = ' + this.filteroptions);
productNameControl.valueChanges.subscribe(selectedProductName => {
this.onProductChange(selectedProductName, indexvalue);
}
);
}
const quantityControl = newProduct.get('quantit');
if (quantityControl) {
quantityControl.valueChanges.subscribe(selectedQuantity => {
this.onQuantityChange(selectedQuantity, indexvalue);
})
}
const priceControl = newProduct.get('price');
if (priceControl) {
priceControl.valueChanges.subscribe((selectedProductName) =>
this.onPriceChange(selectedProductName, indexvalue)
);
}
}
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);
}
get productdetailsarray() {
return this.productFormarray.get('productdetails') as FormArray;
}
ngOnInit() {
this.addNewProduct();
}
onSubmit() {
}
}
Can someone help me for this.
Instead of going for
valueChangeswhich seems to be tedious for form array, instead try the below approach, where we listen for the input changes using(input)event and also reset the autocomplete using(opened)and(click)event where we reset the inputs latest value, by passing the fields as arguments to the functions, please refer the below code/stackblitz for your reference!ts
html
Stackblitz Demo