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 valueChanges
which 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
import { Component, OnInit } from '@angular/core';
import {
FormsModule,
ReactiveFormsModule,
FormGroup,
FormControl,
FormArray,
FormBuilder,
Validators,
} from '@angular/forms';
import { AsyncPipe, CommonModule } from '@angular/common';
import {
MatAutocomplete,
MatAutocompleteModule,
} from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { Observable, map, of, startWith } from 'rxjs';
/**
* @title Highlight the first autocomplete option
*/
@Component({
selector: 'autocomplete-auto-active-first-option-example',
templateUrl: 'autocomplete-auto-active-first-option-example.html',
styleUrl: 'autocomplete-auto-active-first-option-example.css',
standalone: true,
imports: [
FormsModule,
MatFormFieldModule,
MatInputModule,
MatAutocompleteModule,
ReactiveFormsModule,
AsyncPipe,
CommonModule,
],
})
export class AutocompleteAutoActiveFirstOptionExample 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[]>;
resetFilters() {
this.filteroptions = of(this.productlistss);
}
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(event: any, index: number) {
const selectedProductName = event?.option?.value;
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);
}
}
}
}
this.onPriceChange(index);
}
onQuantityChange(event: any, index: number) {
const selectedQuantity = +(event || 0);
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);
}
}
}
}
this.onPriceChange(index);
}
onPriceChange(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) {
// 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();
console.log('filteroption--- = ' + this.filteroptions);
}
performFiltering(input: any) {
if (input.value) {
this.filteroptions = of(this._filter(input.value));
} else {
this.filteroptions = of(this.productlistss);
}
}
clicked(input: any) {
this.filteroptions = of(this.productlistss);
if (input.value) {
this.performFiltering(input);
}
}
onSubmit() {}
}
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"
[id]="'productname_' + i"
[name]="'productname_' + i"
matInput
#input
type="text"
[matAutocomplete]="auto"
class="form-control"
(click)="clicked(input)"
(input)="performFiltering(input)"
(opened)="performFiltering(input)"
/>
<mat-autocomplete
#auto="matAutocomplete"
(optionSelected)="onProductChange($event, i)"
>
<mat-option
*ngFor="let product of filteroptions | async"
[value]="product"
>
{{product}}
</mat-option>
</mat-autocomplete>
</div>
</td>
<td>
<div class="formGroup">
<select
#mySelect
class="form-control"
[id]="'quantit_' + i"
[name]="'quantit_' + i"
formControlName="quantit"
(change)="onQuantityChange(mySelect.value, i)"
>
<option
*ngFor="let quantity of quantitylist"
[value]="quantity"
>
{{quantity}}
</option>
</select>
</div>
</td>
<td>
<div class="formGroup">
<input
type="text"
class="form-control"
[id]="'price_' + i"
[name]="'price_' + i"
formControlName="price"
placeholder="Price "
readonly
/>
</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>