有没有办法为在组件中计算一次的组件创建 css 规则?



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

我可以让它与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 pipe供参考,但我想要一个不需要它的解决方案。

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;
}
}

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

首先,我在模板中有一个表的名称:<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中的样式标签。

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

语法如下所示:

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

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

然后,仅当特定列的列引用更改时,styleRow管道才应重新计算。

最新更新