[CL-343] Create a new table component for virtual scrolling (#10113)
This creates a new component called bit-table-scroll as it's a breaking change in how tables works. We could probably conditionally support both behaviors in the existing table component if we desire. Rather than iterating the rows in the consuming component, we now need to define a row definition, bitRowDef which provides access to the rows data through angular let- syntax. This allows the table component to own the behaviour which is needed in order to use the cdkVirtualFor directive which must be inside the cdk-virtual-scroll-viewport component.
This commit is contained in:
parent
801d9a870b
commit
9b474264e6
|
@ -74,7 +74,7 @@ export class TableDataSource<T> extends DataSource<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(): Observable<readonly T[]> {
|
connect(): Observable<T[]> {
|
||||||
if (!this._renderChangesSubscription) {
|
if (!this._renderChangesSubscription) {
|
||||||
this.updateChangeSubscription();
|
this.updateChangeSubscription();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<cdk-virtual-scroll-viewport
|
||||||
|
scrollWindow
|
||||||
|
[itemSize]="rowSize"
|
||||||
|
[ngStyle]="{ paddingBottom: headerHeight + 'px' }"
|
||||||
|
>
|
||||||
|
<table [ngClass]="tableClass">
|
||||||
|
<thead
|
||||||
|
class="tw-border-0 tw-border-b-2 tw-border-solid tw-border-secondary-300 tw-font-bold tw-text-muted"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<ng-content select="[header]"></ng-content>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *cdkVirtualFor="let r of rows$; trackBy: trackBy" bitRow>
|
||||||
|
<ng-container *ngTemplateOutlet="rowDef.template; context: { $implicit: r }"></ng-container>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</cdk-virtual-scroll-viewport>
|
|
@ -0,0 +1,92 @@
|
||||||
|
import {
|
||||||
|
AfterContentChecked,
|
||||||
|
Component,
|
||||||
|
ContentChild,
|
||||||
|
Input,
|
||||||
|
OnDestroy,
|
||||||
|
TemplateRef,
|
||||||
|
Directive,
|
||||||
|
NgZone,
|
||||||
|
AfterViewInit,
|
||||||
|
ElementRef,
|
||||||
|
TrackByFunction,
|
||||||
|
} from "@angular/core";
|
||||||
|
|
||||||
|
import { TableComponent } from "./table.component";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper directive for defining the row template.
|
||||||
|
*
|
||||||
|
* ```html
|
||||||
|
* <ng-template bitRowDef let-row>
|
||||||
|
* <td bitCell>{{ row.id }}</td>
|
||||||
|
* </ng-template>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: "[bitRowDef]",
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class BitRowDef {
|
||||||
|
constructor(public template: TemplateRef<any>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scrollable table component.
|
||||||
|
*
|
||||||
|
* Utilizes virtual scrolling to render large datasets.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: "bit-table-scroll",
|
||||||
|
templateUrl: "./table-scroll.component.html",
|
||||||
|
providers: [{ provide: TableComponent, useExisting: TableScrollComponent }],
|
||||||
|
})
|
||||||
|
export class TableScrollComponent
|
||||||
|
extends TableComponent
|
||||||
|
implements AfterContentChecked, AfterViewInit, OnDestroy
|
||||||
|
{
|
||||||
|
/** The size of the rows in the list (in pixels). */
|
||||||
|
@Input({ required: true }) rowSize: number;
|
||||||
|
|
||||||
|
/** Optional trackBy function. */
|
||||||
|
@Input() trackBy: TrackByFunction<any> | undefined;
|
||||||
|
|
||||||
|
@ContentChild(BitRowDef) protected rowDef: BitRowDef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Height of the thead element (in pixels).
|
||||||
|
*
|
||||||
|
* Used to increase the table's total height to avoid items being cut off.
|
||||||
|
*/
|
||||||
|
protected headerHeight = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for table header, applies padding on resize.
|
||||||
|
*/
|
||||||
|
private headerObserver: ResizeObserver;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private zone: NgZone,
|
||||||
|
private el: ElementRef,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.headerObserver = new ResizeObserver((entries) => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.headerHeight = entries[0].contentRect.height;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.headerObserver.observe(this.el.nativeElement.querySelector("thead"));
|
||||||
|
}
|
||||||
|
|
||||||
|
override ngOnDestroy(): void {
|
||||||
|
super.ngOnDestroy();
|
||||||
|
|
||||||
|
if (this.headerObserver) {
|
||||||
|
this.headerObserver.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngTemplateOutlet="templateVariable.template; context: { $implicit: rows }"
|
*ngTemplateOutlet="templateVariable.template; context: { $implicit: rows$ }"
|
||||||
></ng-container>
|
></ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -30,7 +30,7 @@ export class TableComponent implements OnDestroy, AfterContentChecked {
|
||||||
|
|
||||||
@ContentChild(TableBodyDirective) templateVariable: TableBodyDirective;
|
@ContentChild(TableBodyDirective) templateVariable: TableBodyDirective;
|
||||||
|
|
||||||
protected rows: Observable<readonly any[]>;
|
protected rows$: Observable<any[]>;
|
||||||
|
|
||||||
private _initialized = false;
|
private _initialized = false;
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export class TableComponent implements OnDestroy, AfterContentChecked {
|
||||||
this._initialized = true;
|
this._initialized = true;
|
||||||
|
|
||||||
const dataStream = this.dataSource.connect();
|
const dataStream = this.dataSource.connect();
|
||||||
this.rows = dataStream;
|
this.rows$ = dataStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,28 +141,28 @@ dataSource.filter = "search value";
|
||||||
### Virtual Scrolling
|
### Virtual Scrolling
|
||||||
|
|
||||||
It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount
|
It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount
|
||||||
of data. This is easily done by wrapping the table in the `cdk-virtual-scroll-viewport` component,
|
of data. This is done by using the `bit-table-scroll` component instead of the `bit-table`
|
||||||
specify a `itemSize`, set `scrollWindow` to `true` and replace `*ngFor` with `*cdkVirtualFor`.
|
component. This component behaves slightly different from the `bit-table` component. Instead of
|
||||||
|
using the `*ngFor` directive to render the rows, you provide a `bitRowDef` template that will be
|
||||||
|
used for rendering the rows.
|
||||||
|
|
||||||
|
Due to limitations in the Angular Component Dev Kit you must provide an `rowSize` which corresponds
|
||||||
|
to the height of each row. If the height of the rows are not uniform, you should set an explicit row
|
||||||
|
height and align vertically.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
|
<bit-table-scroll [dataSource]="dataSource" rowSize="47">
|
||||||
<bit-table [dataSource]="dataSource">
|
<ng-container header>
|
||||||
<ng-container header>
|
<th bitCell bitSortable="id" default>Id</th>
|
||||||
<tr>
|
<th bitCell bitSortable="name">Name</th>
|
||||||
<th bitCell bitSortable="id" default>Id</th>
|
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
|
||||||
<th bitCell bitSortable="name">Name</th>
|
</ng-container>
|
||||||
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
|
<ng-template bitRowDef let-row>
|
||||||
</tr>
|
<td bitCell>{{ row.id }}</td>
|
||||||
</ng-container>
|
<td bitCell>{{ row.name }}</td>
|
||||||
<ng-template let-rows$>
|
<td bitCell>{{ row.other }}</td>
|
||||||
<tr bitRow *cdkVirtualFor="let r of rows$">
|
</ng-template>
|
||||||
<td bitCell>{{ r.id }}</td>
|
</bit-table-scroll>
|
||||||
<td bitCell>{{ r.name }}</td>
|
|
||||||
<td bitCell>{{ r.other }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</bit-table>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Accessibility
|
## Accessibility
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
|
import { ScrollingModule } from "@angular/cdk/scrolling";
|
||||||
import { CommonModule } from "@angular/common";
|
import { CommonModule } from "@angular/common";
|
||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { CellDirective } from "./cell.directive";
|
import { CellDirective } from "./cell.directive";
|
||||||
import { RowDirective } from "./row.directive";
|
import { RowDirective } from "./row.directive";
|
||||||
import { SortableComponent } from "./sortable.component";
|
import { SortableComponent } from "./sortable.component";
|
||||||
|
import { BitRowDef, TableScrollComponent } from "./table-scroll.component";
|
||||||
import { TableBodyDirective, TableComponent } from "./table.component";
|
import { TableBodyDirective, TableComponent } from "./table.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule],
|
imports: [CommonModule, ScrollingModule, BitRowDef],
|
||||||
declarations: [
|
declarations: [
|
||||||
TableComponent,
|
|
||||||
CellDirective,
|
CellDirective,
|
||||||
RowDirective,
|
RowDirective,
|
||||||
SortableComponent,
|
SortableComponent,
|
||||||
TableBodyDirective,
|
TableBodyDirective,
|
||||||
|
TableComponent,
|
||||||
|
TableScrollComponent,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
BitRowDef,
|
||||||
|
CellDirective,
|
||||||
|
RowDirective,
|
||||||
|
SortableComponent,
|
||||||
|
TableBodyDirective,
|
||||||
|
TableComponent,
|
||||||
|
TableScrollComponent,
|
||||||
],
|
],
|
||||||
exports: [TableComponent, CellDirective, RowDirective, SortableComponent, TableBodyDirective],
|
|
||||||
})
|
})
|
||||||
export class TableModule {}
|
export class TableModule {}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { ScrollingModule } from "@angular/cdk/scrolling";
|
|
||||||
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
import { Meta, moduleMetadata, StoryObj } from "@storybook/angular";
|
||||||
|
|
||||||
import { countries } from "../form/countries";
|
import { countries } from "../form/countries";
|
||||||
|
@ -10,7 +9,7 @@ export default {
|
||||||
title: "Component Library/Table",
|
title: "Component Library/Table",
|
||||||
decorators: [
|
decorators: [
|
||||||
moduleMetadata({
|
moduleMetadata({
|
||||||
imports: [TableModule, ScrollingModule],
|
imports: [TableModule],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
argTypes: {
|
argTypes: {
|
||||||
|
@ -114,26 +113,21 @@ export const Scrollable: Story = {
|
||||||
props: {
|
props: {
|
||||||
dataSource: data2,
|
dataSource: data2,
|
||||||
sortFn: (a: any, b: any) => a.id - b.id,
|
sortFn: (a: any, b: any) => a.id - b.id,
|
||||||
|
trackBy: (index: number, item: any) => item.id,
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
|
<bit-table-scroll [dataSource]="dataSource" [rowSize]="43">
|
||||||
<bit-table [dataSource]="dataSource">
|
<ng-container header>
|
||||||
<ng-container header>
|
<th bitCell bitSortable="id" default>Id</th>
|
||||||
<tr>
|
<th bitCell bitSortable="name">Name</th>
|
||||||
<th bitCell bitSortable="id" default>Id</th>
|
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
|
||||||
<th bitCell bitSortable="name">Name</th>
|
</ng-container>
|
||||||
<th bitCell bitSortable="other" [fn]="sortFn">Other</th>
|
<ng-template bitRowDef let-row>
|
||||||
</tr>
|
<td bitCell>{{ row.id }}</td>
|
||||||
</ng-container>
|
<td bitCell>{{ row.name }}</td>
|
||||||
<ng-template body let-rows$>
|
<td bitCell>{{ row.other }}</td>
|
||||||
<tr bitRow *cdkVirtualFor="let r of rows$">
|
</ng-template>
|
||||||
<td bitCell>{{ r.id }}</td>
|
</bit-table-scroll>
|
||||||
<td bitCell>{{ r.name }}</td>
|
|
||||||
<td bitCell>{{ r.other }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</bit-table>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -151,22 +145,16 @@ export const Filterable: Story = {
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
|
<input type="search" placeholder="Search" (input)="dataSource.filter = $event.target.value" />
|
||||||
<cdk-virtual-scroll-viewport scrollWindow itemSize="47">
|
<bit-table-scroll [dataSource]="dataSource" [rowSize]="43">
|
||||||
<bit-table [dataSource]="dataSource">
|
<ng-container header>
|
||||||
<ng-container header>
|
<th bitCell bitSortable="name" default>Name</th>
|
||||||
<tr>
|
<th bitCell bitSortable="value" width="120px">Value</th>
|
||||||
<th bitCell bitSortable="name" default>Name</th>
|
</ng-container>
|
||||||
<th bitCell bitSortable="value" width="120px">Value</th>
|
<ng-template bitRowDef let-row>
|
||||||
</tr>
|
<td bitCell>{{ row.name }}</td>
|
||||||
</ng-container>
|
<td bitCell>{{ row.value }}</td>
|
||||||
<ng-template body let-rows$>
|
</ng-template>
|
||||||
<tr bitRow *cdkVirtualFor="let r of rows$">
|
</bit-table-scroll>
|
||||||
<td bitCell>{{ r.name }}</td>
|
|
||||||
<td bitCell>{{ r.value }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</bit-table>
|
|
||||||
</cdk-virtual-scroll-viewport>
|
|
||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue