mat-table 中 FormArray 的表单组件未正确排序

Form components of FormArray in mat-table not sorting properly

我有一个 mat-table,每行都有表单控件组件,如下图所示:

数据模型如下:

export class ProductClass {
  constructor(
    public id: string,
    public name: string,
    public date: Date,
    public weight: number,
    public input1: string,
    public input2: string
  ) {}
}

每行数据包含 Product IDProduct NameProduct Date 作为 header,以及额外的 child 数据 Weight , Input1, 和 Input 2.

我用下面的示例数据填充 table:

sample: ProductClass[] =
    [
        new ProductClass('PID1', 'Product x', new Date(), 1, 'a', '1'),
        new ProductClass('PID2', 'Product g', new Date(), 2, 'b', '2'),
        new ProductClass('PID3', 'Product s', new Date(), 3, 'c', '3'),
        new ProductClass('PID4', 'Product j', new Date(), 4, 'a', '4'),
        new ProductClass('PID5', 'Product r', new Date(), 5, 'b', '5'),
        new ProductClass('PID6', 'Product w', new Date(), 6, 'c', '6'),
        new ProductClass('PID7', 'Product k', new Date(), 7, 'a', '7'),
    ];

我正在使用 mat-sort-header 启用 table 排序,但我意识到在执行排序时,所有带有静态文本的数据都会相应地排序,除了表单控件组件 input1input2。例如,根据上面的示例数据,我的 input1 值为 "a,b,c,a,b,c,a",因此如果我将 table 排序为 Product ID 降序,则 input1 值假设按 "a,c,b,a,c,b,a" 的顺序排列,但事实并非如此。排序后,input1值在"a,b,b,a,c,c,a"的顺序,这就没有意义了。但是如果我把 input1 的值作为像 {{child.value.input1}} 这样的静态文本,它会显示正确的值

模板

<form [formGroup]="productForm">
  <main>
    <section class="container-fluid">
      <table mat-table formArrayName="productsArray" [dataSource]="tableDetails" multiTemplateDataRows
        class="maTable maTable-bordered" matSort>

        <ng-container matColumnDef="id">
          <mat-header-cell *matHeaderCellDef mat-sort-header> Product ID </mat-header-cell>
          <mat-cell *matCellDef="let row"> {{row.value.id}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="name">
          <mat-header-cell *matHeaderCellDef mat-sort-header> Product Name </mat-header-cell>
          <mat-cell *matCellDef="let row"> {{row.value.name}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="date">
          <mat-header-cell *matHeaderCellDef mat-sort-header> Product Date </mat-header-cell>
          <mat-cell *matCellDef="let row"> {{row.value.date | date:'dd-MMM-yyyy'}} </mat-cell>
        </ng-container>

        <ng-container matColumnDef="expandedDetail">
          <mat-cell *matCellDef="let child ; let rowindex = dataIndex" [attr.colspan]="tableColumns.length"
            [formGroupName]="getActualIndex(rowindex)">
            <div class="col-sm-6 mt-3">
              <div class="row">
                <h6>Weight: </h6>
                <p class="rounded-sm"> {{child.value.weight}}KG </p>
              </div>
              <div class="row">
                <h6>Input 1</h6><br/>
                <input type="radio" formControlName="input1" value="a">
                <label for="a">A</label>

                <input type="radio" formControlName="input1" value="b">
                <label for="b">B</label>

                <input type="radio" formControlName="input1" value="c">
                <label for="c">C</label>
              </div>
              {{child.value.input1}}

              <div class="row pb-1">
                <h6>Input 2</h6>
                <textarea formControlName="input2" class="rounded-sm border-light-gray px-4" aria-label="Remark" maxlength="500"></textarea>
                {{child.value.input2}}
              </div>
            </div>
          </mat-cell>
        </ng-container>

        <mat-header-row *matHeaderRowDef="tableColumns"></mat-header-row>
        <mat-row matRipple *matRowDef="let child; columns: tableColumns;" class="element-row"></mat-row>
        <mat-row *matRowDef="let row ; columns: ['expandedDetail'];" style="overflow: hidden"></mat-row>
      </table>
      <mat-paginator [pageSizeOptions]="[5, 10, 25, 100]"></mat-paginator>
    </section>
  </main>
</form>

组件

import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { FormBuilder, FormGroup, AbstractControl } from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, AfterViewInit {
  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  search: string = '';
  tableColumns: string[] = ['id', 'name', 'date'];
  tableDetails: MatTableDataSource<AbstractControl>;
  productArray = this.fb.array([]);
  productForm = this.fb.group({
    productsArray: this.productArray
  });
  sample: ProductClass[] = [
    new ProductClass('PID1', 'Product x', new Date(), 1, 'a', '1'),
    new ProductClass('PID2', 'Product g', new Date(), 2, 'b', '2'),
    new ProductClass('PID3', 'Product s', new Date(), 3, 'c', '3'),
    new ProductClass('PID4', 'Product j', new Date(), 4, 'a', '4'),
    new ProductClass('PID5', 'Product r', new Date(), 5, 'b', '5'),
    new ProductClass('PID6', 'Product w', new Date(), 6, 'c', '6'),
    new ProductClass('PID7', 'Product k', new Date(), 7, 'a', '7')
  ];

  constructor(private fb: FormBuilder) {}

  ngOnInit(): void {
    for (const product of this.sample) {
      this.productArray.push(
        this.fb.group({
          id: product.id,
          name: product.name,
          date: product.date,
          weight: product.weight,
          input1: product.input1,
          input2: product.input2
        })
      );
    }
  }

  protected init(): void {
    this.tableDetails = new MatTableDataSource();
    this.tableDetails.data = this.productArray.controls;
    this.tableDetails.paginator = this.paginator;
    this.tableDetails.sort = this.sort;

    this.tableDetails.sortingDataAccessor = (
      item: AbstractControl,
      property
    ) => {
      switch (property) {
        case 'date':
          return new Date(item.value.date);
        default:
          return item.value[property];
      }
    };

    const filterPredicate = this.tableDetails.filterPredicate;
    this.tableDetails.filterPredicate = (data: AbstractControl, filter) => {
      return filterPredicate.call(this.tableDetails, data.value, filter);
    };
  }

  ngAfterViewInit(): void {
    this.init();
  }

  applyFilter(): void {
    this.tableDetails.filter = this.search.trim().toLowerCase();
  }

  getActualIndex(index: number): number {
    return index + this.paginator.pageSize * this.paginator.pageIndex;
  }
}

export class ProductClass {
  constructor(
    public id: string,
    public name: string,
    public date: Date,
    public weight: number,
    public input1: string,
    public input2: string
  ) {}
}

我试图在 Stackbitz 上复制我的代码,虽然 CSS 不起作用,但它确实复制了我上面描述的问题。

更新

我注意到只有在分页的多页中填充数据时才存在问题。例如,我的分页大小是每页 5 行,如果数据超过 5,就会有问题。如果我将分页大小更改为大于填充数据,以便所有行都呈现在一页中,那么就不会有问题。任何帮助将不胜感激!

事实证明这是 FormGroupName 的问题,您无法将 FormGroupName 设置为 dataIndex,就像您将 table 排序为 [=13] 一样=],只对UI层的table进行排序,并没有改变数据集的实际顺序(tableDetails.data的顺序)

所以我将 [formGroupName]="getActualIndex(rowindex)" 改为 [formGroupName]="tableDetails.data.indexOf(child)"