有没有一种方法可以为在组件内部计算一次的组件创建CSS规则?

时间:2019-09-04 13:52:25

标签: angular angular-material

我正在尝试查看是否有可能动态定义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;
    }
}

2 个答案:

答案 0 :(得分:0)

尝试向*ngFor添加trackBy函数。如果您不使用此方法为每列提供一些标识符,则每次列表引用发生更改时都会重新渲染整个列表。

语法如下:

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

在您的情况下,getColumnId方法似乎可以返回column.columnDef

styleRow管道仅应在特定列的列引用更改时重新评估。

答案 1 :(得分: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中的样式标签。