Browse Source

Merge pull request #23670 from davidsi02/rel-9.3

feat(extensible-table): Add infinite scroll support
pull/23700/head
sumeyye 5 months ago
committed by GitHub
parent
commit
7d9fee1b65
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html
  2. 65
      npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

5
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.html

@ -7,6 +7,11 @@
(activate)="tableActivate.emit($event)"
(select)="onSelect($event)"
[selected]="selected"
(scroll)="onScroll($event)"
[scrollbarV]="infiniteScroll"
[style.height]="getTableHeight()"
[loadingIndicator]="infiniteScroll && isLoading"
[footerHeight]="infiniteScroll ? false : 50"
>
@if(selectable) {
<ngx-datatable-column [width]="50" [sortable]="false" [canAutoResize]="false" [draggable]="false" [resizeable]="false">

65
npm/ng-packs/packages/components/extensible/src/lib/components/extensible-table/extensible-table.component.ts

@ -10,16 +10,16 @@ import {
Input,
LOCALE_ID,
OnChanges,
OnDestroy,
Output,
signal,
SimpleChanges,
TemplateRef,
TrackByFunction,
ViewChild,
} from '@angular/core';
import { AsyncPipe, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';
import { Observable, filter, map } from 'rxjs';
import { Observable, filter, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs';
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import { NgxDatatableModule, SelectionType } from '@swimlane/ngx-datatable';
@ -75,7 +75,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
templateUrl: './extensible-table.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewInit {
export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewInit, OnDestroy {
readonly #injector = inject(Injector);
readonly getInjected = this.#injector.get.bind(this.#injector);
protected readonly cdr = inject(ChangeDetectorRef);
@ -113,11 +113,17 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
this._selectionType = typeof value === 'string' ? SelectionType[value] : value;
}
_selectionType: SelectionType = SelectionType.multiClick;
@Input() selected: any[] = [];
@Output() selectionChange = new EventEmitter<any[]>();
// Infinite scroll configuration
@Input() infiniteScroll = false;
@Input() isLoading = false;
@Input() scrollThreshold = 10;
@Output() loadMore = new EventEmitter<void>();
@Input() tableHeight: number;
hasAtLeastOnePermittedAction: boolean;
readonly propList: EntityPropList<R>;
@ -129,6 +135,12 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
// Signal for actions column width
private readonly _actionsColumnWidth = signal<number | undefined>(DEFAULT_ACTIONS_COLUMN_WIDTH);
// Infinite scroll: debounced load more subject
private readonly loadMoreSubject = new Subject<void>();
private readonly loadMoreSubscription = this.loadMoreSubject
.pipe(debounceTime(100), distinctUntilChanged())
.subscribe(() => this.triggerLoadMore());
readonly columnWidths = computed(() => {
const actionsColumn = this._actionsColumnWidth();
const widths = [actionsColumn];
@ -216,7 +228,6 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
return record;
});
}
isVisibleActions(rowData: any): boolean {
@ -247,10 +258,50 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
this.selectionChange.emit(selected);
}
onScroll(scrollEvent: Event): void {
if (!this.shouldHandleScroll()) {
return;
}
const target = scrollEvent.target as HTMLElement;
if (!target) {
return;
}
if (this.isNearScrollBottom(target)) {
this.loadMoreSubject.next();
}
}
private shouldHandleScroll(): boolean {
return this.infiniteScroll && !this.isLoading;
}
private isNearScrollBottom(element: HTMLElement): boolean {
const { offsetHeight, scrollTop, scrollHeight } = element;
return offsetHeight + scrollTop >= scrollHeight - this.scrollThreshold;
}
private triggerLoadMore(): void {
this.loadMore.emit();
}
getTableHeight() {
if (!this.infiniteScroll) return 'auto';
return this.tableHeight ? `${this.tableHeight}px` : 'auto';
}
ngAfterViewInit(): void {
this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => {
if (!this.infiniteScroll) {
this.list?.requestStatus$?.pipe(filter(status => status === 'loading')).subscribe(() => {
this.data = [];
this.cdr.markForCheck();
});
}
}
ngOnDestroy(): void {
this.loadMoreSubscription.unsubscribe();
}
}

Loading…
Cancel
Save