4

我正在寻找一个带有固定标题的工作虚拟滚动表,所以我发现 Cdk 很棒,但文档真的很难理解。目前我正在尝试将 with 结合CdkTable起来CdkVirtualScoll

我能找到的所有工作示例都使用材料表,但我没有。

那我怎么CdkVirtualScoll去上班呢?这是我到目前为止所做的(来自示例):

<cdk-virtual-scroll-viewport>
<cdk-table [dataSource]="dataSource">
    <ng-container cdkColumnDef="username">
        <cdk-header-cell *cdkHeaderCellDef> User name </cdk-header-cell>
        <cdk-cell *cdkCellDef="let row"> {{row.username}} </cdk-cell>
    </ng-container>

    <ng-container cdkColumnDef="title">
        <cdk-header-cell *cdkHeaderCellDef> Title </cdk-header-cell>
        <cdk-cell *cdkCellDef="let row"> {{row.title}} </cdk-cell>
    </ng-container>

    <!-- Header and Row Declarations -->
    <cdk-header-row *cdkHeaderRowDef="['username', 'age']"></cdk-header-row>
    <cdk-row *cdkRowDef="let row; columns: ['username', 'age']"></cdk-row>
</cdk-table>
</cdk-virtual-scroll-viewport>

正如文档中所写的那样,表格被包装到滚动视口中。但是我如何以及在哪里设置*cdkVirtualFor现在?

谢谢你的帮助!

4

2 回答 2

0

因为我找不到真正可行的解决方案,所以我为固定标头编写了自己的“快速而肮脏”的代码。不过,我希望将来能找到更好的方法。也许下一个 Cdk 版本会提供一个解决方案。

我现在所做的是编写一个(或多或少 hack)指令,从内部克隆表cdk-virtual-scroll-viewport并将克隆的节点放置在之前。在下一步visibility中,table thead元素的 设置为collapse

用法:

<cdk-virtual-scroll-viewport [itemSize]="30" cloneThead>
    <table class="table table-hover">
        <thead>
            ...
        </thead>
        <tbody>
            <tr *cdkVirtualFor="let item of list">
                <td>...</td>
                ...
            </tr>
        </tbody>
    </table>
</cdk-virtual-scroll-viewport>

cloneThead指令非常简单:

import { Directive, AfterViewInit, ElementRef } from '@angular/core';

@Directive({
    selector: '[cloneThead]'
})

export class CloneDirective implements AfterViewInit{

    constructor(private el: ElementRef) {}

    ngAfterViewInit(){
        let cloned = this.el.nativeElement.cloneNode(true);

        let table = cloned.querySelector("table");
            table.style.position = 'sticky';
            table.style.top = '0';
            table.style.zIndex = '100';

        this.el.nativeElement.appendChild(table);
    }
}

这工作得很好,但仍然存在一个大问题:创建克隆后ngAfterViewInit,其表行cdkVirtualFor尚未创建到 DOM 的效果。

这对克隆本身有好处,因为它还不包含 的任何tr元素tbody,但是对于元素的正确宽度计算出的 CSS 样式th也是未知的。

所以所有th元素都需要有一个 CSS width 属性。否则th宽度和td宽度可能会不同 - 这看起来很难看......

cdk-virtual-scroll-viewport在-table 被绘制之后,也许其他人有一个解决方案来制作“真正的”克隆。

于 2019-02-24T08:05:57.090 回答
0

这是一个更新的解决方案

前面代码的一个大问题是它无法动态计算列的宽度。所以有必要为每一列指定一个 with 。

这个版本修复了这个问题。

@Directive({
  selector: '[cdkFixedHeader]'
})

export class FixedHeaderDirective implements AfterViewInit{

    constructor(private el: ElementRef, private renderer:Renderer2) {}

    ngAfterViewInit(){

        // get the viewport element
        let cdkViewport = this.el.nativeElement.closest("cdk-virtual-scroll-viewport");

        // check if table was already cloned
        let clonedHeader = cdkViewport.querySelectorAll('.cloned-header');

        // create a clone if not exists
        if (clonedHeader.length == 0)
        {
            let table = this.el.nativeElement.closest('table');
            let cloned = table.cloneNode(true);
                cloned.style.position = 'sticky';
                cloned.style.top = '0';
                cloned.style.zIndex = '100';

            // remove tbody with elements
            let tbody = cloned.querySelector('tbody');
            cloned.removeChild(tbody);

            // add a "helper" class
            this.renderer.addClass(cloned, "cloned-header");

            // append cloned object to viewport
            cdkViewport.appendChild(cloned);
        }       
        // 
        //  walk through all <tr> with their <td> and store the max value in an array
        //
        let width = [];
        let td = this.el.nativeElement.querySelectorAll("td");
        width = new Array(td.length).fill(0);

        td.forEach((item,index) => {
            const w = item.getBoundingClientRect().width;
            width[index] = Math.max(w, width[index]);
        })  
        // 
        //  get <th> elements and apply the max-width values
        //
        let th = cdkViewport.querySelectorAll('.cloned-header th');
        th.forEach((item,index) => {
            this.renderer.setStyle(item, "min-width", width[index] + 'px')
        })
    }
}

用法:

用法已稍作更改,因为在处理时需要调用指令*cdkVirtualFor

<tr *cdkVirtualFor="let item of list" cdkFixedHeader>
    ...
</tr>

而已!不是很好,但工作......

于 2019-02-24T11:30:15.190 回答