如何使用* ngFor过滤Angular中未知数量的元素?

时间:2018-02-10 01:49:09

标签: angular typescript rxjs angular-reactive-forms angular-forms

我需要创建一个搜索输入,用于过滤每个父帐户中的子帐户列表。目前,输入任一输入都会过滤所有帐户,而不是仅关联相关帐户。

实例(StackBlitz)
Basic (no FormArray)
With FormArray

要求

  1. 帐户和子帐户的数量未知(1 ... *)
  2. 每个帐户都需要HTML中自己的搜索输入(FormControl)
  3. 输入输入A应仅过滤帐户A的列表 输入输入B应仅过滤帐户B的列表。
  4. 问题

    1. 如何确保每个FormControl仅过滤当前* ngFor上下文中的帐户?

    2. 如何独立观看未知数量的FormControls以进行值更改?我意识到我可以观看FormArray,但我希望有更好的方法。

    3. 理想情况下,解决方案应该:

      1. 使用反应表单
      2. 值更改时发出Observable
      3. 允许动态地从表单添加/删除FormControl

4 个答案:

答案 0 :(得分:1)

参考Angular.io - Pipe - Appendix: No FilterPipe or OrderByPipe

  

Angular不提供这样的管道,因为它们表现不佳并且可以防止侵略性缩小。

根据这个建议,我会用方法替换管道过滤器。

正如@Claies所说,您还需要单独存储搜索条件 由于在编译时帐户数量未知,因此使用Array(this.accounts.length)初始化searchTerm数组并使用this.searchTerms[accountIndex] || ''处理空的searchTerms。

<强> app.component.ts

export class AppComponent {
  accounts = [
    {
      accountNumber: '12345',
      subAccounts: [ 
        { accountNumber: '123' },
        { accountNumber: '555' },
        { accountNumber: '123555' }
      ]
    },
    {
      accountNumber: '55555',
      subAccounts: [
        { accountNumber: '12555' },
        { accountNumber: '555' }
      ]
    }
  ];

  searchTerms = Array(this.accounts.length)

  filteredSubaccounts(accountNo, field) {
    const accountIndex = this.accounts.findIndex(account => account.accountNumber === accountNo);
    if (accountIndex === -1) {
      // throw error
    }
    const searchTerm = this.searchTerms[accountIndex] || '';    
    return this.accounts[accountIndex].subAccounts.filter(item => 
      item[field].toLowerCase().includes(searchTerm.toLowerCase()));
  }
}

<强> app.component.html

<div>
    <div *ngFor="let account of accounts; let ind=index" class="act">
        <label for="search">Find an account...</label>
        <input id="search" [(ngModel)]="searchTerms[ind]" />
        <div *ngFor="let subAccount of filteredSubaccounts(account.accountNumber, 'accountNumber'); let i=index">
            <span>{{subAccount.accountNumber}}</span>
        </div>
    </div>
</div>

参考:StackBlitz

答案 1 :(得分:1)

在上一个答案中,将在每次更改检测时调用过滤器函数(基本上浏览器中与此组件无关的任何事件),这可能是错误的。一种更具角度和高效的方法是利用Observable的力量:

batchListFromQuery = session.createSQLQuery(sql).addEntity(TBatchEntry.class).list();

从内存写入平板电脑,如果通过复制粘贴无效,请使用ide提示查找错误。 :)

通过这种方式,您甚至可以将API调用传递到同一个observable中,从而无需订阅&amp;取消订阅自己,因为异步管道处理所有这些。

编辑:要使其适应2输入,您可以从两者合并.fromEvent(),并在过滤器调用中根据evt.target.id更改行为。对不完整的例子很抱歉,在平板电脑上编写代码非常糟糕:D

RXJS合并:https://www.learnrxjs.io/operators/combination/merge.html

答案 2 :(得分:1)

这是我解决问题的方法。

首先,您遍历外部帐户并为每个帐户创建专用formControl并将其存储在FormGroup中。作为ID参考,我使用了帐号。有了这个,我宣布了一个名为getSearchCtrl(accountNumber)的函数来检索右formControl

使用[formControlName]="account.accountNumber"将您的模板与FormGroup中提供的formControl相关联。

使用formControlgetSearchCtrl(account.accountNumber) filter引用正确的pipe并传递该值。

<div *ngFor="let subAccount of account.subAccounts | filter : 'accountNumber': getSearchCtrl(account.accountNumber).value; let i=index"> <span>{{subAccount.accountNumber}}</span> </div>

我还编辑了你的stackblitz app:https://stackblitz.com/edit/angular-reactive-filter-4trpka?file=app%2Fapp.component.html

我希望这个解决方案可以帮到你。

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  searchForm: FormGroup;
  searchTerm = '';
  loaded = false;

  // Test Data. The real HTTP request returns one or more accounts.
  // Each account has one or more sub-accounts.
  accounts = [/* test data see stackblitz*/]

  public getSearchCtrl(accountNumber) {
    return this.searchForm.get(accountNumber)
  }

  constructor() {
    const group: any = {}
    this.accounts.forEach(a => {
      group[a.accountNumber] = new FormControl('')
    });
    this.searchForm = new FormGroup(group);
    this.loaded = true;
  }

  ngOnInit() {
    this.searchForm.valueChanges.subscribe(value => {
      console.log(value);
    });
  }
}
<ng-container *ngIf="loaded; else loading">

  <div [formGroup]="searchForm">
	  <div *ngFor="let account of accounts; let ind=index" class="act">
		  <label for="search">Find an account...</label>
		  <input id="search" [formControlName]="account.accountNumber" />
		  <div *ngFor="let subAccount of account.subAccounts | filter : 'accountNumber': getSearchCtrl(account.accountNumber).value; let i=index">
			  <span>{{subAccount.accountNumber}}</span>
		  </div>
	  </div>
  </div>
</ng-container>

<ng-template #loading>LOADING</ng-template>

答案 3 :(得分:0)

另一种方法是创建一个帐户组件来封装每个帐户及其搜索。在这种情况下,不需要管道 - Example app

import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-account',
  template: `
    <label for="search">Find an account...</label>
    <input id="search" [(ngModel)]="searchTerm" />
    <div *ngFor="let subAccount of subAccounts()">
      <span>{{subAccount.accountNumber}}</span>
    </div>
    <br/>
  `
})
export class AccountComponent {

  @Input() account;
  @Input() field;
  private searchTerm = '';

  subAccounts () {
    return this.account.subAccounts
      .filter(item => item[this.field].toLowerCase()
        .includes(this.searchTerm.toLowerCase())
    ); 
  }
}

父母将是

import { Component } from '@angular/core';
@Component({
  selector: 'my-app',
  template: `
    <div>
      <div>
        <app-account *ngFor="let account of accounts;"
          class="act" 
          [account]="account" 
          [field]="'accountNumber'">
        </app-account>
      </div>
    </div>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  accounts = [
    ...
  ];
}