在 angular 中的 formArray 中使用 ngx-mat-select-search 时动态设置 mat-select-options 8

Dynamically set mat-select-options when using ngx-mat-select-search in formArray in angular 8

我有一个包含两个 mat-select 的 formArray。其中之一使用 ngx-mat-select-search 从 mat-select 的选项中的值进行搜索。我的问题是,当表单数组包含多个元素时,我从这些元素中搜索其中一个 mat-select,当我搜索时,所有 mat-select 的值都消失了,它会在值被 selected 后重新出现。 这是我的模板文件的一部分:

<ng-container formArrayName='cpUserAccounts'
                        *ngFor='let account of cpUserAccounts.controls; let i=index'>
                        <div class="d-flex align-items-center flex-wrap col-md-12" [formGroupName]='i'>
                            <div class="col-sm-6">
                                <mat-form-field class="task-array">
                                    <mat-label>Client</mat-label>
                                    <mat-select formControlName="clientId" required>
                                        <mat-option>
                                            <ngx-mat-select-search [formControl]="bankFilterCtrl"
                                                placeholderLabel='Search' noEntriesFoundLabel="'No match found'">
                                            </ngx-mat-select-search>
                                        </mat-option>
                                        <mat-option *ngFor="let client of filteredOptions | async" [value]="client.id">
                                            {{client.companyName}}
                                        </mat-option>
                                    </mat-select>
                                    <mat-hint class="error"
                                        *ngIf="findDuplicate(account.controls.clientId.value, 'cpUserAccounts')">
                                        {{constants.errorMessage.duplicateClientLabel}}
                                    </mat-hint>
                                </mat-form-field>
                            </div>
                            <div class="col-sm-4">
                                <mat-form-field class="task-array">
                                    <mat-label>Role</mat-label>
                                    <mat-select formControlName="roleId" required>
                                        <mat-option *ngFor="let role of accountRoles" [value]="role.id">
                                            {{role.name}}
                                        </mat-option>
                                    </mat-select>
                                </mat-form-field>
                            </div>
                            <div class="col-sm-2 d-flex justify-content-end">
                                <mat-icon class="remove-task-button" title="Remove" (click)='removeAccount(i)'
                                    matSuffix>
                                    remove
                                </mat-icon>
                            </div>
                        </div>
                    </ng-container>

.ts 文件包含以下代码:

filteredOptions: ReplaySubject<ClientModel[]> = new ReplaySubject<ClientModel[]>(1);
public bankFilterCtrl: FormControl = new FormControl();
protected onDestroy = new Subject<void>();

ngOnInit() {
      this.bankFilterCtrl.valueChanges
      .pipe(takeUntil(this.onDestroy))
      .subscribe(() => {
      this.filterBanks();
            });
    }

getClients() {
        const subscription = this.clientContactService.getClients().subscribe(clients => {
            this.clients = clients.data;
            this.filterBanks();
        });
        this.subscription.push(subscription);
    }

protected filterBanks() {
        if (!this.clients) {
            return;
        }
        // get the search keyword
        let search = this.bankFilterCtrl.value;
        if (!search) {
            this.filteredOptions.next(this.clients.slice());
            return;
        } else {
            search = search.toLowerCase();
        }
        // filter the clients
        this.filteredOptions.next(
            this.clients.filter(client => client.companyName.toLowerCase().indexOf(search) > -1)
        );
   }

以下是正在发生的事情的流程图像: 一开始,它是这样的:

然后:

问题出在这里:

因为您使用的是 *ngFor 中的 filteredOptions | async 元素,相同的选项将用于显示的每个“client”-select 元素。因此在过滤的时候,会过滤掉所有客户端select框的选项,这样原来selected的值就没有了

要解决此问题,您可以将最外层 *ngFor 的内容移动到一个单独的组件,该组件应实现 ControlValueAccessor 接口 (https://angular.io/api/forms/ControlValueAccessor#description)。

我也被困在这个问题上,找不到任何解决方案,然后自己想出了这个办法。最简单的方法是使用一个以数组作为值的备份 FormControl。并在要使用该数组的模板数组中访问它的值。尝试理解这个简单的代码示例 -

export class FormExampleComponent {
  public formGroup: FormGroup;
  public itemCodesArray: ItemCode[] = [
    { id: 1, name: apple },
    { id: 2, name: banna },
    { id: 3, name: pineapple },
  ];
  
  constructor(fb: FormBuilder) {}
  
  public ngOnInit(): void {
    this.createForm();
  }

  private createForm() {
    this.formGroup = this.fb.group({
      formItems: this.fb.array([this.initFormGroup()]),
    });
  }

  private initFormGroup(): FormGroup {
    return this.fb.group({
      description: [null, []],
      formItemsGroup: this.initFormItemsGroup(),
    });
  }

  private initFormItemsGroup(): FormGroup {
    const fg = this.fb.group({
      // id is just to recognize the form array elemnts by trackByfunction
      id: [new Date().getTime() + Math.floor(Math.random() * 100)],
      itemCodeSearch: [null, []],
      // temp array 
      filteredItemCodes: [this.itemCodesArray, []],
      itemCodeId: [null, [Validators.required]],
      itemDescription: [null, [Validators.required]],
    });

    fg.get('itemCodeSearch')
      .valueChanges
      .subscribe((searchText) => {
        this.itemCodes$.subscribe((itemCode) => {
          if (searchText) {
            fg.get('filteredItemCodes').setValue(
              itemCode.filter((item) => item.name.toLowerCase().includes(searchText.toLowerCase())),
            );
          } else {
            fg.get('filteredItemCodes').setValue(itemCode);
          }
        });
      });

    return fg;
  }
  
  public get formItems(): AbstractControl[] {
    return (this.formGroup.get('formItems') as FormArray).controls;
  }
  
  public trackByFunction(index: number, item: any): number {
    return item.value.formItemsGroup.id;
  }
}
<form [formGroup]="formGroup">
  <div formArrayName="formItems">
    <div *ngFor="let formItem of formItems; let i = index; trackBy: trackByFunction">
      <div [formGroupName]="i">
        <div>
          <div formGroupName="poItemsGroup">
          
            <mat-form-field appearance="outline">
              <mat-label>Description</mat-label>
              <input type="text" matInput formControlName="description" required />
            </mat-form-field>
              
            <mat-form-field appearance="outline">
              <mat-label>Item Code</mat-label>
              <mat-select formControlName="itemCodeId" required>
                <mat-option>
                  <ngx-mat-select-search
                    formControlName="itemCodeSearch"
                    placeholderLabel="Search item"
                    noEntriesFoundLabel="No item found"
                  ></ngx-mat-select-search>
                </mat-option>
                <mat-option
                  *ngFor="
                    let item of formItem.get('formItemsGroup').get('filteredItemCodes').value;
                    trackBy: 'id' | trackByKey
                  "
                  [value]="item.id"
                >
                  {{ item.name }}
                </mat-option>
              </mat-select>
            </mat-form-field>
            
            <mat-form-field appearance="outline">
              <mat-label>Item Description</mat-label>
              <input type="text" matInput formControlName="itemDescription" required />
            </mat-form-field>
              
          </div>
        </div>
      </div>
    </div>
  </div>
</form>

由于 valueChanges 触发过于频繁,您可以使用 debounceTime 来阻止它在用户输入的每个字符上触发。
您还可以使用 OnDestroy 生命周期钩子和 takeUntil 函数来取消订阅每个订阅,以提高效率。