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

问题描述 投票:0回答:1

我有一个

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 ID
Product Name
Product Date
作为标题,以及附加子数据
Weight
Input1
Input 2


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

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
来启用表格排序,但我意识到在执行排序时,所有具有静态文本的数据都会相应地排序,除了表单控制组件
input1
input2
。例如,根据上面的示例数据,我的
input1
值为
"a,b,c,a,b,c,a"
,因此,如果我按
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 行的容纳范围,就会出现问题。

如果我将分页大小更改为大于显示的数据量,以便所有行都可以呈现在单个页面上,则不会出现问题。

任何帮助将不胜感激!

angular typescript angular-material angular-reactive-forms formarray
1个回答
4
投票

原来是

FormGroupName
的问题,你不能用
FormGroupName
来设置你的
dataIndex
,因为当你用
mat-sort-header
对表格进行排序时,它只在UI层对表格进行排序,没有改变数据集的实际顺序(
tableDetails.data
的顺序)

所以我将其改为

[formGroupName]="getActualIndex(rowindex)"
,而不是
[formGroupName]="tableDetails.data.indexOf(child)"

© www.soinside.com 2019 - 2024. All rights reserved.