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

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

问题描述

我有一个包含某些字段的表单数组,其中一个是输入.在此字段中,根据用户输入,我们在 API 中进行搜索并返回一些数据.

问题是,它只适用于第一次动态控制.当我添加更多控件时,它对它们不起作用.

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

那还有什么选择呢.

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

提前谢谢

.ts

 purchaseform = this.fb.group({vendor_mobile : ['', [Validators.required, Validators.minLength(10), Validators.maxLength(10), Validators.pattern("^[0-9]*$")]],产品:this.fb.array([this.addProductGroup()])})添加产品组(){返回 this.fb.group({product_name : ['', Validators.required ],product_quantity : ['', [Validators.required, Validators.pattern("^[0-9]*$") ]],product_Buyingprice : ['', [ Validators.required, Validators.pattern("^[0-9]*$") ]],})}获取产品(){将 this.purchaseform.get('product') 作为 FormArray 返回;}添加产品(){this.product.push(this.addProductGroup())}移除产品(索引){返回 this.product.removeAt(index)}ngAfterViewInit() {//服务端搜索fromEvent(this.input.nativeElement,'keyup').管道(过滤器(布尔),去抖动时间(500),distinctUntilChanged(),点击((事件:键盘事件)=> {控制台日志(事件)console.log(this.input.nativeElement.value)this.productService.search_Products(this.input.nativeElement.value).subscribe(data =>{如果(数据){this.product_list = 数据console.log(this.product_list)}})})).订阅();}

.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>输入产品名称</mat-label><input matInput #input咏叹调标签=产品名称"[matAutocomplete]="自动"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">此类产品不存在</mat-option></mat-autocomplete></mat-form-field><mat-form-field class="example-full-width"><mat-label>输入产品数量</mat-label><input matInput formControlName="product_quantity" type="number" ></mat-form-field><mat-form-field class="example-full-width"><mat-label>输入产品价格</mat-label><input matInput formControlName="product_Buyingprice" type="number"></mat-form-field><button type="button" [disabled]="!purchaseform.valid" class="btn btn-primary" (click) = "addproduct()">添加产品</button><button [disabled] = "i==0" type="button" class="btn btn-danger" (click) = "remove_product(i)">删除产品</button></ng-容器>

</表单>

解决方案

如果使用 @ViewChild,viewChild 只会得到第一个元素.

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

@ViewChildren('input') 输入:QueryListthis.inputs.forEach(input=>{fromEvent(input.nativeElement,'keyup')})

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

方法不是使用 fromEvents.这个想法来自 这个 Amir Tugendhaft 的条目博客.第一个是不使用productList",否则是 productList 和异步管道的可观察对象.由于我们有几个productList",我们需要一个可观察的数组

productList$:Observable[]=[];

.html 会像

<ng-container [formGroupName]="i"><mat-form-field class="example-full-width"><mat-label>输入产品名称</mat-label><input matInput #input咏叹调标签=产品名称"[matAutocomplete]="自动"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 &&结果?.length<=0" class="text-danger">此类产品不存在</mat-option><ng-容器></mat-autocomplete></mat-form-field></ng-容器>

看看我们如何使用 <ng-container *ngIf="product_list$[i] |async as results"> 并迭代results".

好吧,下一步是更改函数 addProductGroup 以创建 observable 和 asing 到数组 productList$

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

addProductGroup(index) {//我们使用辅助常量const group = this.fb.group({product_name: ["", Validators.required],产品数量:["",[Validators.required, Validators.pattern("^[0-9]*$")]],产品_购买价格:["",[Validators.required, Validators.pattern("^[0-9]*$")]]});//查看 observables 将如何成为product_name"的 valueChange//这个formGroup,使用管道和switchMapthis.productList$[index] = group.get("product_name").valueChanges.pipe(去抖动时间(300),switchMap(value => this.productService.search_Products(value)));//最后,我们返回组返回组;}

最后,当调用 addGroup 将索引"作为参数发送时要小心,所以,首先

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

还有

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

你可以看到stackblitz(我模拟了使用的服务,显然你调用了一个 API)

注意:如果您想使用 ngbTypeHead,请参阅 这篇文章

更新[displayWith]="displayFn"

的使用

我用 mat-option 写的

这使得值是属性状态",如果我们想要整个对象,我们需要写

但我们也需要做一点改变,首先是在matAutocomplete中添加displaywith

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

我们的函数可以像,例如

displayFn(data: any): string {返回数据 ?data.name+'-'+data.state : '';}

第二个是当我们收到一个对象或一个字符串时,更改服务中的一些search_Products".我们可以用类似的函数替换

search_Products(name: any): Observable{//name 可以是字符串或对象,所以name=name.toLowerCase?name.toLowerCase():name.name//现在name是一个字符串,可以过滤或调用appireturn of(states.filter(x=>x && x.toLowerCase().indexOf(name)>=0)).pipe(map(result=>){return result.map(x=>({state:x,name:x}))}))}

分叉了stackblitz 与此更改

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>

解决方案

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

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

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