1

我正在尝试查看是否可以动态定义 mat-table 的列,包括列的宽度和对齐方式。

我可以使它与 ngStyle 一起工作,如果我使用管道,则不会经常调用代码:

            <ng-container *ngFor="let column of columns" matColumnDef="{{column.columnDef}}">
                  <th mat-header-cell *matHeaderCellDef mat-sort-header >
                      {{column.header}}
                  </th>
                  <td mat-cell *matCellDef="let row" [ngStyle]="column | styleRow">
                      {{column.cell(row)}}
                  </td>
            </ng-container>

styleRow是一个管道,它转换列定义列表中的一行并为该列返回正确的 css 规则;例如

    transform(column: ColumnConfig): object {
        const obj = {
            textAlign: column.textAlign,
            width: column.type === 'number' ?
                column.length + 'ch' :
                (column.length * 0.45) + 'rem'
        };
        console.log(column, obj);
        return obj;
    }

问题是,根据我的口味,它仍然经常被称为方式,当我所需要的只是将以下规则放入组件的样式表中时:

        .mat-column-foo {
            width: 7em;
        }

        .mat-column-bar {
            width: 15em;
        }

        .mat-column-baz {
            width: 7em;
            text-align: right;
        }
        // ... and so on.

...但我不知道该怎么做。在以下示例中,我希望 setStyles 函数为:

  • 将 css 规则添加到我在 test.component.css 中找到规则的相同样式标记中

  • 使用规则创建一个新的样式标签,但我必须找到组件的 _ngcontent_xxxxxx 属性,这样我才能创建看起来像的规则.mat-column-foo[_ngcontent_xxxxxx] {...}

export interface TblData {
    foo: string;
    bar: string;
    baz: number;
}

export interface ColumnConfig {
    columnDef: string;
    header: string;
    textAlign: string;
    length: number;
    type: string;
    cell: any;
}
const ELEMENT_DATA: TblData[] = [ .... ];

@Component({
    selector: 'app-test',
    templateUrl: './test.component.html',
    styleUrls: ['./test.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestComponent implements OnInit {
    columns: ColumnConfig[] = [
        {   columnDef: 'foo',
            header: 'Foo',
            textAlign: 'left',
            length: 10,
            type: 'date',
            cell: (element: any) => `${element.foo}`
        },
        {   columnDef: 'bar',
            header: 'Bar',
            textAlign: 'left',
            length: 50,
            type: 'string',
            cell: (element: any) => `${element.bar}`
        },
        {   columnDef: 'baz',
            header: 'Baz',
            textAlign: 'right',
            length: 7,
            type: 'number',
            cell: (element: any) => `${element.baz}`
        }
    ];
    displayedColumns = ['select', ...this.columns.map(c => c.columnDef)];
    dataSource = new MatTableDataSource<TblData>(ELEMENT_DATA);
    selection = new SelectionModel<TblData>(true, []);
    @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
    @ViewChild(MatSort, {static: true}) sort: MatSort;

    constructor() {
    }

    ngOnInit() {
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
        // setStyles(columns);  <-- how do I implement this function ?
    }

    .....

示例的完整来源:

html:

<div>
    <div class="mat-elevation-z8">
        <div class="table_scroll">
            <div class="table_intro">
                <p>
                    Some text above the table.
                </p>
                <p>
                    And more text.
                </p>
            </div>
            <table mat-table [dataSource]="dataSource" matSort>

                <!-- Checkbox Column -->
                <ng-container matColumnDef="select">
                    <th mat-header-cell *matHeaderCellDef>
                        <mat-checkbox (change)="$event ? masterToggle() : null"
                                      [checked]="selection.hasValue() && isAllSelected()"
                                      [indeterminate]="selection.hasValue() && !isAllSelected()"
                                      [aria-label]="checkboxLabel()">
                        </mat-checkbox>
                    </th>
                    <td mat-cell *matCellDef="let row">
                        <mat-checkbox (click)="$event.stopPropagation()"
                                      (change)="$event ? selection.toggle(row) : null"
                                      [checked]="selection.isSelected(row)"
                                      [aria-label]="checkboxLabel(row)">
                        </mat-checkbox>
                    </td>
                </ng-container>

                <!-- Dynamic Columns -->
                <ng-container *ngFor="let column of columns; trackBy: columnId" matColumnDef="{{column.columnDef}}">
                    <th mat-header-cell *matHeaderCellDef mat-sort-header>
                        {{column.header}}
                    </th>
                    <td mat-cell *matCellDef="let row" [ngStyle]="column | styleRow">
                        {{column.cell(row) | formatRow:column}}
                    </td>
                </ng-container>

                <tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
                <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
            </table>
        </div>
        <mat-toolbar>
            <mat-toolbar-row>
                <mat-icon title="Export as CSV">table_chart</mat-icon>
                &nbsp;&nbsp;&nbsp;
                <mat-icon title="Export as PDF">print</mat-icon>
                <span class="example-spacer"></span>
                <mat-paginator class="paginator" [pageSizeOptions]="[50, 100, 200]"></mat-paginator>
            </mat-toolbar-row>
        </mat-toolbar>
    </div>
</div>

TS:

import {ChangeDetectionStrategy, Component, Input, OnInit, ViewChild} from '@angular/core';
import {MatTableDataSource} from '@angular/material/table';
import {SelectionModel} from '@angular/cdk/collections';
import {MatPaginator} from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort';

/**
 * Column definition.
 * columnDef is the key name in the row record
 * header is the text displayed in the column's header
 * textAlign and length are for styling the columns
 * type is for future use
 * cell is a function to get that cell's value from the row.
 */
export interface ColumnConfig {
    columnDef: string;
    header: string;
    textAlign: string;
    length: number;
    type: string;
    cell: any;
}

/**
 * Displayed rows must have a selection label property.
 */
export interface MrmTableRow {
    selection_label: string;
}


@Component({
    selector: 'app-mrm-table',
    templateUrl: './mrm-table.component.html',
    styleUrls: ['./mrm-table.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class MrmTableComponent<T extends MrmTableRow> implements OnInit {
    @Input() columns: ColumnConfig[];
    @Input() data: T[];

    selection = new SelectionModel<T>(true, []);
    @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
    @ViewChild(MatSort, {static: true}) sort: MatSort;
    public dataSource: MatTableDataSource<T>;
    public displayedColumns: string[];

    constructor() {
    }

    ngOnInit() {
        this.dataSource = new MatTableDataSource<T>(this.data);
        this.displayedColumns = ['select', ...this.columns.map(c => c.columnDef)];
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
        // setStyles(data);
    }

    /**
     *  returns true if all rows are selected
     */
    isAllSelected() {
        const numSelected = this.selection.selected.length;
        const numRows = this.dataSource.data.length;
        return numSelected === numRows;
    }

    /**
     * Selects all rows if they are not all selected; otherwise clear selection.
     */
    masterToggle() {
        this.isAllSelected() ?
            this.selection.clear() :
            this.dataSource.data.forEach(row => this.selection.select(row));
    }

    /** The aria label (accessibility) for the checkbox on the passed row */
    checkboxLabel(row?: T): string {
        if (!row) {
            return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
        }
        return `${this.selection.isSelected(row) ?
            'deselect' :
            'select'} row ${row.selection_label} `;
    }

    /**
     * This for the trackBy in the column loop.
     */
    columnId(index: number, item: ColumnConfig): string {
        return item.columnDef;
    }
}

SCSS:

.padded {
  padding: 15px;
  background-color: #fafafa;
}
.example-spacer {
  flex: 1 1 auto;
}



:host > div {

    padding: 10px;

    & > div {
        .mat-toolbar {
            background-color: transparent;
        }

        width: 48em;

        .table_intro {
            padding: 25px;
        }
        div.table_scroll {
            height: 600px;
            overflow: auto;
        }
    }
}

styleRow 管道供参考,但我想要一个不需要它的解决方案。

import {Pipe, PipeTransform} from '@angular/core';
import {ColumnConfig} from './mrm-table/mrm-table.component';

@Pipe({
    name: 'styleRow'
})
export class StyleRowPipe implements PipeTransform {

    transform(column: ColumnConfig): object {
        const obj = {
            textAlign: column.textAlign,
            width: column.type === 'number' ?
                column.length + 'ch' :
                (column.length * 0.55) + 'rem'
        };
        console.log('styleRow', column, obj);
        return obj;
    }
}
4

2 回答 2

0

尝试将trackBy函数添加到*ngFor. 如果您不使用它为每列提供一些标识符,则每次列表引用更改时都会重新呈现整个列表。

语法如下:

<ng-container *ngFor="let column of columns; trackBy: getColumnId(column)">

看起来该getColumnId方法可以column.columnDef在您的情况下返回。

只有当特定列的列参考发生变化时,styleRow管道才应重新评估。

于 2019-09-04T14:06:07.297 回答
0

我可能已经找到了一种方法来做我想做的事。

首先,我在模板中为表命名: <table mat-table [dataSource]="dataSource" matSort #table>

然后我在组件中检索到对表的引用: @ViewChild('table', { read: ElementRef , static: true}) tableRef: ElementRef;

最后,我得到了用于 css 规则隔离的属性,计算 css 规则并注入它们:

    setStyles(columns: ColumnConfig[]) {
        // Get the specific attribute for the component
        const attrNames: string[] = Array.from(this.tableRef.nativeElement.attributes)
            .map((val: Attr) => val.name)
            .filter((val: string) => val.startsWith('_ngcontent'));

        // Hopefully there should be only 1 attribute that starts with _ngcontent.
        const attr: string =  attrNames[0];

        // Now I can make the css rules
        const css: string = columns.map((col) => {
            const width = col.type === 'number' ?
                col.length + 'ch' :
                (col.length * 0.55) + 'rem';
            return `
                .mat-column-${col.columnDef}[${attr}] {
                    text-align: ${col.textAlign};
                    width: ${width};
                }`;
        }).join('\n');

        // And inject them in the page
        const head = document.getElementsByTagName('head')[0];
        const style = document.createElement('style');
        style.type = 'text/css';
        style.appendChild(document.createTextNode(css));
        head.appendChild(style);
    }

有用; 现在我必须找到一种方法来删除 ngOnDestroy 中的样式标签。

于 2019-09-05T14:47:58.403 回答