仅对第一个动态控件执行的Angular 9 Formarray搜索操作 [英] Angular 9 Formarray search operation executing for only first dynamic control

查看:87
本文介绍了仅对第一个动态控件执行的Angular 9 Formarray搜索操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有某些字段的formarray,其中之一是输入字段.在此字段中,根据用户输入,我们将搜索API并返回一些数据.

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.

我想这是因为我已经在ngAfterViewInit()中编写了搜索逻辑.

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

但是那有什么选择呢?

我不知道如何解决这个问题.

I can't get how to solve this problem.

先谢谢您

.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>

推荐答案

如果使用@ViewChild,则viewChild仅获得第一个元素.

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

如果您使用@ViewChildren,则需要为QueryList的每个元素创建这么多事件

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')
})

无论如何,如果数组最初是固定元素,则此方法不起作用.好吧,您可以订阅input.changes和bla-bla-bla

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

不使用fromEvents方法.这个想法来自此Amir Tugendhaft的入门博客.第一个不是使用"productList",而是可观察到的productList和异步管道.因为我们有几个"productList",所以我们需要一个可观察的数组

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>[]=[];

.html就像

<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>

查看我们如何使用<ng-container *ngIf="product_list$[i] |async as results">并遍历结果".

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

好吧,下一步是更改函数addProductGroup以创建可观察的对象并将其赋值为数组productList $

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

方法是订阅控件的valueChanges,但使用switchMap返回服务的响应

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;
  }

最后,在调用addGroup作为参数发送"index"时要格外小心,

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"
    });

还有

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

您可以看到 stackblitz (我使用of来模拟服务,显然您要调用API)

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

注意:如果要使用ngbTypeHead,请参见

NOTE: If you want use ngbTypeHead see this post

更新使用[displayWith]="displayFn"

我在mat-option中写了

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"  >

我们的功能可以是例如

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

第二个是当我们收到一个对象或一个字符串时,在考虑服务的情况下更改一些"search_Products".我们可以将功能替换为

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

这篇关于仅对第一个动态控件执行的Angular 9 Formarray搜索操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆