Как объединить cdkVirtualScroll с cdkTable?
Я ищу рабочую виртуальную таблицу прокрутки с фиксированными заголовками, поэтому я нашел Cdk, который великолепен, но документация действительно сложна для подражания. На данный момент я пытаюсь объединить 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
сейчас?
Спасибо за вашу помощь!
2 ответа
Поскольку я не мог найти реально работающее решение, я написал свой "быстрый и грязный" код для фиксированного заголовка. Тем не менее я надеюсь найти гораздо лучший путь в будущем. Возможно, следующая версия Cdk предложит решение.
Теперь я написал (более или менее хакерскую) директиву, которая клонирует таблицу изнутри 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
Таблица была нарисована.
Вот обновленное решение
большая проблема предыдущего кода заключалась в том, что он не мог динамически вычислять ширину столбцов. Поэтому необходимо было указать с каждым столбцом.
Эта версия исправляет эту проблему.
@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>
Это оно! Не очень хорошо, но работает...