@@ -25,91 +25,107 @@ import { makeData, type Person } from './make-data'
2525 imports : [ FlexRenderDirective ] ,
2626 template : `
2727 <p>
28- For tables, the basis for the offset of the translate css function is from
29- the row's initial position itself. Because of this, we need to calculate
30- the translateY pixel count different and base it off the the index.
28+ Table rows can't be absolutely positioned, so only the visible rows are
29+ rendered, with a spacer row above and below them. The spacers keep the
30+ table as tall as all of its rows, so the sticky header stays in place
31+ while scrolling.
3132 </p>
3233 <br />
3334
3435 <div #scrollElement class="list scroll-container">
35- <div [style.height.px]="virtualizer.getTotalSize()">
36- <table>
37- <thead>
38- @for (
39- headerGroup of table.getHeaderGroups();
40- track headerGroup.id
41- ) {
42- <tr>
43- @for (header of headerGroup.headers; track header.id) {
44- <th
45- [attr.colspan]="header.colSpan"
46- [style.width.px]="header.getSize()"
47- >
48- @if (!header.isPlaceholder) {
49- <div
50- [class.cursor-pointer]="header.column.getCanSort()"
51- [class.select-none]="header.column.getCanSort()"
52- (click)="
53- header.column.getToggleSortingHandler()?.($event)
54- "
55- >
56- <ng-container
57- *flexRender="
58- header.column.columnDef.header;
59- props: header.getContext();
60- let headerText
61- "
62- >
63- <span>{{ headerText }}</span>
64- {{ getSortIcon(header.column.getIsSorted()) }}
65- </ng-container>
66- </div>
67- }
68- </th>
69- }
70- </tr>
71- }
72- </thead>
73- <tbody>
74- @for (
75- virtualRow of virtualizer.getVirtualItems();
76- track data[virtualRow.index].id
77- ) {
78- <tr
79- [style.height.px]="virtualRow.size"
80- [style.transform]="
81- 'translateY(' +
82- (virtualRow.start - $index * virtualRow.size) +
83- 'px)'
84- "
85- >
86- @for (
87- cell of rows()[virtualRow.index].getVisibleCells();
88- track cell.id
89- ) {
90- <td>
91- <ng-container
92- *flexRender="
93- cell.column.columnDef.cell;
94- props: cell.getContext();
95- let cellText
36+ <table>
37+ <thead>
38+ @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
39+ <tr>
40+ @for (header of headerGroup.headers; track header.id) {
41+ <th
42+ [attr.colspan]="header.colSpan"
43+ [style.width.px]="header.getSize()"
44+ >
45+ @if (!header.isPlaceholder) {
46+ <div
47+ [class.cursor-pointer]="header.column.getCanSort()"
48+ [class.select-none]="header.column.getCanSort()"
49+ (click)="
50+ header.column.getToggleSortingHandler()?.($event)
9651 "
9752 >
98- <span>{{ cellText }}</span>
99- </ng-container>
100- </td>
101- }
102- </tr>
103- }
104- </tbody>
105- </table>
106- </div>
53+ <ng-container
54+ *flexRender="
55+ header.column.columnDef.header;
56+ props: header.getContext();
57+ let headerText
58+ "
59+ >
60+ <span>{{ headerText }}</span>
61+ {{ getSortIcon(header.column.getIsSorted()) }}
62+ </ng-container>
63+ </div>
64+ }
65+ </th>
66+ }
67+ </tr>
68+ }
69+ </thead>
70+ <tbody>
71+ @if (paddingTop() > 0) {
72+ <tr>
73+ <td
74+ [attr.colspan]="table.getVisibleLeafColumns().length"
75+ [style.height.px]="paddingTop()"
76+ ></td>
77+ </tr>
78+ }
79+ @for (
80+ virtualRow of virtualizer.getVirtualItems();
81+ track data[virtualRow.index].id
82+ ) {
83+ <tr [style.height.px]="virtualRow.size">
84+ @for (
85+ cell of rows()[virtualRow.index].getVisibleCells();
86+ track cell.id
87+ ) {
88+ <td>
89+ <ng-container
90+ *flexRender="
91+ cell.column.columnDef.cell;
92+ props: cell.getContext();
93+ let cellText
94+ "
95+ >
96+ <span>{{ cellText }}</span>
97+ </ng-container>
98+ </td>
99+ }
100+ </tr>
101+ }
102+ @if (paddingBottom() > 0) {
103+ <tr>
104+ <td
105+ [attr.colspan]="table.getVisibleLeafColumns().length"
106+ [style.height.px]="paddingBottom()"
107+ ></td>
108+ </tr>
109+ }
110+ </tbody>
111+ </table>
107112 </div>
108113 ` ,
109114 styles : `
110115 .scroll-container {
111116 height: 600px;
112117 overflow: auto;
118+ overflow-anchor: none;
119+ }
120+
121+ table {
122+ border-collapse: collapse;
123+ }
124+
125+ thead {
126+ position: sticky;
127+ top: 0;
128+ background: white;
113129 }
114130 ` ,
115131} )
@@ -191,4 +207,18 @@ export class AppComponent {
191207 estimateSize : ( ) => 34 ,
192208 overscan : 20 ,
193209 } ) )
210+
211+ // Height of the spacer rows rendered above and below the visible rows
212+ paddingTop = computed ( ( ) => {
213+ const virtualRows = this . virtualizer . getVirtualItems ( )
214+ return virtualRows . length > 0 ? virtualRows [ 0 ] . start : 0
215+ } )
216+
217+ paddingBottom = computed ( ( ) => {
218+ const virtualRows = this . virtualizer . getVirtualItems ( )
219+ return virtualRows . length > 0
220+ ? this . virtualizer . getTotalSize ( ) -
221+ virtualRows [ virtualRows . length - 1 ] . end
222+ : 0
223+ } )
194224}
0 commit comments