Autocomplete search filter not working for dynamically added input fields in angular

50 Views Asked by At

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.

1

There are 1 best solutions below

4
Naren Murali On BEST ANSWER

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>

Stackblitz Demo