Я работаю над модулем биллинга, в котором есть поля ввода, которые добавляются динамически. Здесь я использую фильтр поиска с автозаполнением для динамически добавляемых полей ввода (название продукта).
Но поиск с автозаполнением работает нормально, если есть одно поле с названием продукта. Когда я добавляю более одной информации о ProductName, правильно работает только последнее добавленное поле. Когда я пытаюсь изменить предыдущее поле названия продукта, оно не работает.
Ниже 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" 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>
Ниже машинописного кода
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.info('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() {
}
}
Может ли кто-нибудь помочь мне в этом?
Да, но код зависит от вопроса, который я упомянул





Вместо того, чтобы использовать valueChanges, что кажется утомительным для массива формы, вместо этого попробуйте приведенный ниже подход, где мы прослушиваем изменения входных данных, используя событие (input), а также сбрасываем автозаполнение, используя события (opened) и (click), где мы сбрасываем последнее значение входных данных, передавая поля в качестве аргументов функций, для справки обратитесь к приведенному ниже коду/stackblitz!
тс
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.info('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>
Но когда мы удаляем любые записи с помощью кнопки «Удалить», значения имени продукта вычисляются неправильно. Поскольку индексы варьируются, я думаю
@Reegan У меня нет никаких проблем, stackblitz показывает правильное значение!
Добавьте три строки и укажите запись, затем удалите первую строку и попробуйте изменить название продукта, цена и количество не отразятся.
Это здорово. Единственная проблема: если в поле ввода названия продукта есть пустые или несовпадающие продукты, в поле цены отображается предыдущее значение.
Пожалуйста, не добавляйте весь свой код, а только связанные с ним элементы. Пожалуйста Создайте минимальный воспроизводимый пример.