Angular 9 Formarray search operation executing for only first dynamic control

if you use @ViewChild, viewChild only get the first element.

if you’re using @ViewChildren you need get create so many event for each Element of the QueryList

@ViewChildren('input') inputs:QueryList<ElementRef>
this.inputs.forEach(input=>{
  fromEvent(input.nativeElement,'keyup')
})

Anyway this NOT work -works if the array was fixed elements at first. Well, you can subscribe to inputs.changes and bla-bla-bla

The way is NOT use fromEvents. The idea goes from this Amir Tugendhaft’s entry blog. The first is not use a “productList” else an observable of productList and async pipe. As we has severals “productList, we need an array of observables

productList$:Observable<any>[]=[];

And the .html will be like

<div formArrayName = "product"  *ngFor="let prod of product?.controls; let i = index">       
      <ng-container [formGroupName]="i">

         <mat-form-field class="example-full-width">
            <mat-label>Enter product name</mat-label>

            <input matInput #input
                   aria-label="product name"
                   [matAutocomplete]="auto"
                   formControlName ="product_name">
            <mat-autocomplete #auto="matAutocomplete">
                <ng-container *ngIf="product_list$[i] |async as results">
                    <mat-option *ngFor="let state of results " [value]="state.state">
                        <span>{{state.name}}</span> 
                    </mat-option>
                    <mat-option *ngIf="prod.get('product_name').value &&
                           results?.length<=0" class="text-danger">
                         Such product does not exists
                    </mat-option>
                <ng-container>
            </mat-autocomplete>
          </mat-form-field>
      </ng-container>
</div>

See how we use <ng-container *ngIf="product_list$[i] |async as results"> and iterate over “results”.

Well, the next step is change the function addProductGroup to create the observable and asing to the array productList$

The way is subscribe to valueChanges of the control, but return the response of the service using switchMap

addProductGroup(index) {
    //we use an auxiliar const
    const group = this.fb.group({
      product_name: ["", Validators.required],
      product_quantity: [
        "",
        [Validators.required, Validators.pattern("^[0-9]*$")]
      ],
      product_Buyingprice: [
        "",
        [Validators.required, Validators.pattern("^[0-9]*$")]
      ]
    });

    //See how the observables will be valueChange of the "product_name"
    //of this formGroup, using a pipe and switchMap

    this.productList$[index] = group.get("product_name").valueChanges.pipe(
      debounceTime(300),
      switchMap(value => this.productService.search_Products(value))
    );

    //finally, we return the group
    return group;
  }

At last, be carefull when call to addGroup to send as argument the “index”, so, at first

this.purchaseform = this.fb.group({
      ...
      product: this.fb.array([this.addProductGroup(0)]) //<--see the "0"
    });

And

  addproduct() {
    this.product.push(this.addProductGroup(this.product.length));
  }

You can see the stackblitz (I simulate the service using of, obviously you call to an API)

NOTE: If you want use ngbTypeHead see this post

Update use of [displayWith]="displayFn"

I wrote in mat-option

<mat-option *ngFor="let state of results " [value]="state.state">

This make that the value is the property “state”, if we want the whole object we need write

<mat-option *ngFor="let state of results" [value]="state">

but also we need make a little change, the first is add in the matAutocomplete the displaywith

<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"  >

And our function can be like, e.g.

displayFn(data: any): string {
    return data ?data.name+'-'+data.state : '';
  }

The second is change a few the “search_Products” in the service taking account when we received an object or an string. We can replace the function with some like

search_Products(name: any): Observable<any> {
    //name can be a string or an object, so
    name=name.toLowerCase?name.toLowerCase():name.name

    //now name is a string and can filter or call to the appi

    return of(states.filter(x=>x && x.toLowerCase().indexOf(name)>=0)).pipe(map(result=>
    {
      return result.map(x=>({state:x,name:x}))
    }))
  }

I forked the stackblitz with this changes

Leave a Comment