Фильтр поиска автозаполнения не работает для динамически добавляемых полей ввода в angular

Я работаю над модулем биллинга, в котором есть поля ввода, которые добавляются динамически. Здесь я использую фильтр поиска с автозаполнением для динамически добавляемых полей ввода (название продукта).

Но поиск с автозаполнением работает нормально, если есть одно поле с названием продукта. Когда я добавляю более одной информации о 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() {
  }
}

Может ли кто-нибудь помочь мне в этом?

Пожалуйста, не добавляйте весь свой код, а только связанные с ним элементы. Пожалуйста Создайте минимальный воспроизводимый пример.

Get Off My Lawn 27.03.2024 14:26

Да, но код зависит от вопроса, который я упомянул

Reegan 27.03.2024 15:00
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
2
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вместо того, чтобы использовать 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>

Демо-версия Stackblitz

Но когда мы удаляем любые записи с помощью кнопки «Удалить», значения имени продукта вычисляются неправильно. Поскольку индексы варьируются, я думаю

Reegan 27.03.2024 16:33

@Reegan У меня нет никаких проблем, stackblitz показывает правильное значение!

Naren Murali 27.03.2024 18:02

Добавьте три строки и укажите запись, затем удалите первую строку и попробуйте изменить название продукта, цена и количество не отразятся.

Reegan 28.03.2024 08:13

Это здорово. Единственная проблема: если в поле ввода названия продукта есть пустые или несовпадающие продукты, в поле цены отображается предыдущее значение.

Reegan 28.03.2024 13:36

Другие вопросы по теме