Angular 9 Formarray search operation executing for only first dynamic control

醉酒当歌 提交于 2020-05-17 06:26:09

问题


I have a formarray with certain fields one of which is input. In this field, on user input, we search in the API's and return some data.

The problem is, it is working only for first dynamic control. When I add more controls, it is not working for them.

I guess this is happening because I have written search logic in ngAfterViewInit().

But what's the alternative then.

I can't get how to solve this problem.

Thank you In advance

.ts

    purchaseform = this.fb.group({

      vendor_mobile : ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern("^[0-9]*$")]],
      product : this.fb.array([this.addProductGroup()])

    })

    addProductGroup() {
      return this.fb.group({
        product_name : ['', Validators.required ],
        product_quantity : ['', [Validators.required, Validators.pattern("^[0-9]*$") ]],
        product_Buyingprice : ['', [ Validators.required, Validators.pattern("^[0-9]*$") ]],
      })
        }

get product() {
    return this.purchaseform.get('product') as FormArray;
  }

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

   remove_product(index) {
     return this.product.removeAt(index)
   }

    ngAfterViewInit() {
        // server-side search
    fromEvent(this.input.nativeElement,'keyup')
        .pipe(
            filter(Boolean),
            debounceTime(500),
            distinctUntilChanged(),
            tap((event:KeyboardEvent) => {
              console.log(event)
              console.log(this.input.nativeElement.value)

              this.productService.search_Products(this.input.nativeElement.value).subscribe(data =>{
                if(data){
                  this.product_list = data
                  console.log(this.product_list)
                }
              })
            })
        )
        .subscribe();
    }

.html

<form [formGroup]="purchaseform"> 
// other fields

<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" [displayWith]="displayFn" >

              <mat-option *ngFor="let state of product_list " [value]="state ">
                <span>{{state.name}}</span> 
              </mat-option>

              <mat-option *ngIf="!product_list || !product_list.length" class="text-danger">
                Such product does not exists
              </mat-option>

            </mat-autocomplete>

          </mat-form-field>

       <mat-form-field class="example-full-width">
        <mat-label>Enter product quantity</mat-label>
          <input matInput formControlName="product_quantity" type="number" >
        </mat-form-field>


        <mat-form-field class="example-full-width">
          <mat-label>Enter product price</mat-label>
        <input matInput formControlName="product_Buyingprice" type="number">
        </mat-form-field>


<button type="button" [disabled]="!purchaseform.valid" class="btn btn-primary"  (click) = "addproduct()">Add product</button>               
    <button [disabled] = "i==0" type="button" class="btn btn-danger" (click) = "remove_product(i)">Delete product</button>

      </ng-container>
</div>

</form>

回答1:


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



来源:https://stackoverflow.com/questions/61503425/angular-9-formarray-search-operation-executing-for-only-first-dynamic-control

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!