Angular Material mat-table 如何在组件中定义可重用的列?

9

有没有人知道是否可以创建一个“列”组件,以便与mat-table一起使用?我尝试创建一个常用的列定义组件,但是当将其添加到表格中时,会出现无法找到列选择器的错误。我的列定义如下:

@Component({
  selector: 'iam-select-column',
  template: `
  <ng-container matColumnDef="select">
    <mat-header-cell *matHeaderCellDef>
      <mat-checkbox></mat-checkbox>
    </mat-header-cell>
    <mat-cell *matCellDef="let row">
      <mat-checkbox></mat-checkbox>
    </mat-cell>
  </ng-container>
  `,
  styles: [`
  `]
})
export class SelectColumnComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

在表格中使用此功能。
<mat-table class="mat-elevation-z8">

  <iam-select-column></iam-select-column>

  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>

</mat-table>

显示列为:

  displayedColumns = [
    'select'
  ];

我想知道是否有可能实现这一点,因为我想避免在表格中重复出现选择列的情况。

2个回答

22
为了使其正常工作,您需要使用 table.addColumnDef 方法将该 columnDef 手动添加到表中。
@Component({
  selector: 'iam-select-column',
  template: `
    <ng-container matColumnDef="select">
        ...
    </ng-container>
  `
})
export class SelectColumnComponent implements OnInit {
  @ViewChild(MatColumnDef) columnDef: MatColumnDef;

  constructor(@Optional() public table: MatTable<any>, private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    if (this.table) {
      this.cdRef.detectChanges();
      this.table.addColumnDef(this.columnDef);
    }
  }
}

在执行这个操作之前,我们必须确保指令matColumnDef已经完成绑定初始化,以便它有name属性。为此,我们必须在该组件上运行detectChanges。

Ng-run示例

另一种方法是在父组件中提供该名称,如angular material问题描述的那样https://github.com/angular/material2/issues/13808#issuecomment-434417804

parent.html

<mat-table class="mat-elevation-z8">

  <iam-select-column name="select"></iam-select-column>

SelectColumnComponent

@Input()
get name(): string { return this._name; }
set name(name: string) {
    this._name = name;
    this.columnDef.name = name;
}

1
这是我在使用Angular 12和@angular/material 12时的解决方案。 这段代码基于来自https://github.com/angular/components/issues/5889的代码片段。
@Component({
  selector: 'app-column-template',
  template: `
    <ng-container matColumnDef>
      <th mat-header-cell *matHeaderCellDef>{{ label || capitalize(name) }}</th>
      <td mat-cell *matCellDef="let row">
        <ng-container *ngTemplateOutlet="cellTemplate; context: { $implicit: row }"></ng-container>
      </td>
    </ng-container>
  `,
  // eslint-disable-next-line @angular-eslint/no-host-metadata-property
  host: {
    class: 'column-template cdk-visually-hidden',
    '[attr.ariaHidden]': 'true',
  },
})
export class ColumnTemplateComponent implements OnDestroy, OnInit {
  @Input() name = '';
  @Input() label: string | null = null;
  @Input() align: 'before' | 'after' = 'before';

  constructor(@Optional() public table: MatTable<unknown>) {}

  @ViewChild(MatColumnDef, { static: true }) columnDef!: MatColumnDef;
  @ViewChild(MatCellDef, { static: true }) cellDef!: MatCellDef;
  @ViewChild(MatHeaderCellDef, { static: true }) headerCellDef!: MatHeaderCellDef;
  @ViewChild(MatFooterCellDef, { static: true }) footerCellDef!: MatFooterCellDef;

  @ContentChild('cell', { static: false })
  cellTemplate: TemplateRef<unknown> | null = null;

  ngOnInit(): void {
    if (this.table && this.columnDef) {
      this.columnDef.name = this.name;
      this.columnDef.cell = this.cellDef;
      this.columnDef.headerCell = this.headerCellDef;
      this.columnDef.footerCell = this.footerCellDef;
      this.table.addColumnDef(this.columnDef);
    }
  }

  ngOnDestroy(): void {
    if (this.table) {
      this.table.removeColumnDef(this.columnDef);
    }
  }

  capitalize(value: string): string {
    return value.charAt(0).toUpperCase() + value.slice(1);
  }
}

export type CellValueNeededFn = (data: Record<string, unknown>, name: string) => string;

@Component({
  selector: 'app-column',
  template: `
    <ng-container matColumnDef>
      <th mat-header-cell *matHeaderCellDef>{{ label || capitalize(name) }}</th>
      <td mat-cell *matCellDef="let row">{{ getCellValue(row) }}</td>
    </ng-container>
  `,
  // eslint-disable-next-line @angular-eslint/no-host-metadata-property
  host: {
    class: 'column cdk-visually-hidden',
    '[attr.ariaHidden]': 'true',
  },
})
export class ColumnComponent implements OnDestroy, OnInit {
  @Input() name = '';
  @Input() label: string | null = null;
  @Input() align: 'before' | 'after' = 'before';
  @Input() cellValueNeeded: CellValueNeededFn | null = null;

  constructor(@Optional() public table: MatTable<unknown>) {}

  @ViewChild(MatColumnDef, { static: true }) columnDef!: MatColumnDef;
  @ViewChild(MatCellDef, { static: true }) cellDef!: MatCellDef;
  @ViewChild(MatHeaderCellDef, { static: true }) headerCellDef!: MatHeaderCellDef;
  @ViewChild(MatFooterCellDef, { static: true }) footerCellDef!: MatFooterCellDef;

  @ContentChild('cell', { static: false })
  cellTemplate: TemplateRef<unknown> | null = null;

  ngOnInit(): void {
    if (this.table && this.columnDef) {
      this.columnDef.name = this.name;
      this.columnDef.cell = this.cellDef;
      this.columnDef.headerCell = this.headerCellDef;
      this.columnDef.footerCell = this.footerCellDef;
      this.table.addColumnDef(this.columnDef);
    }
  }

  ngOnDestroy(): void {
    if (this.table) {
      this.table.removeColumnDef(this.columnDef);
    }
  }

  capitalize(value: string): string {
    return value.charAt(0).toUpperCase() + value.slice(1);
  }

  getCellValue(row: Record<string, unknown>): unknown {
    return this.cellValueNeeded ? this.cellValueNeeded(row, this.name) : row[this.name];
  }
}

当我试图基于ColumnTemplateComponent构建ColumnComponent时,最终结果是熟悉的错误。

Error: Could not find column with id "...".
     at getTableUnknownColumnError (table.js:1078) [angular]
     blah-blah-blah...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接