Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
afterNextRender,
AfterViewInit,
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
inject,
Injector,
LOCALE_ID,
Expand All @@ -17,9 +19,10 @@ import {
contentChild,
viewChild,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { AsyncPipe, isPlatformBrowser, NgComponentOutlet, NgTemplateOutlet } from '@angular/common';

import { Observable, filter, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs';
import { Observable, map, Subject, debounceTime, distinctUntilChanged } from 'rxjs';

import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
import {
Expand Down Expand Up @@ -92,6 +95,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
})
export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestroy {
readonly #injector = inject(Injector);
readonly #destroyRef = inject(DestroyRef);
readonly getInjected = this.#injector.get.bind(this.#injector);
protected readonly locale = inject(LOCALE_ID);
protected readonly config = inject(ConfigStateService);
Expand Down Expand Up @@ -186,6 +190,10 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
},
);

private horizontalScrollOffset = 0;

private hasPendingHorizontalScrollOffset = false;

hasAtLeastOnePermittedAction: boolean;

readonly propList: EntityPropList<R>;
Expand Down Expand Up @@ -232,6 +240,7 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
}

this._data.set(dataValue.map((record, index) => this.prepareRecord(record, index)));
this.restoreHorizontalScrollOffset();
});
}

Expand Down Expand Up @@ -363,13 +372,66 @@ export class ExtensibleTableComponent<R = any> implements AfterViewInit, OnDestr
ngAfterViewInit(): void {
if (!this.infiniteScroll()) {
this.list()
?.requestStatus$?.pipe(filter(status => status === 'loading'))
.subscribe(() => {
this._data.set([]);
?.requestStatus$?.pipe(takeUntilDestroyed(this.#destroyRef))
.subscribe(status => {
if (status === 'loading') {
this.rememberHorizontalScrollOffset();
this._data.set([]);
return;
}

// A failed request never reaches the data input, restore from here instead
if (status === 'error') {
this.restoreHorizontalScrollOffset();
}
});
}
}

private getBodyElement(): HTMLElement | null {
if (!this.isBrowser) {
return null;
}

return this.table()?.element?.querySelector('datatable-body') ?? null;
}

// The element carrying the column width is gone while loading without rows, so the browser
// resets the horizontal scroll position and the header keeps the offset it had before
private rememberHorizontalScrollOffset(): void {
const body = this.getBodyElement();
this.hasPendingHorizontalScrollOffset = true;

// Not scrollable while a previous request is still in flight, keep the offset taken back then
if (body && body.scrollWidth > body.clientWidth) {
this.horizontalScrollOffset = body.scrollLeft;
}
}

private restoreHorizontalScrollOffset(): void {
if (!this.hasPendingHorizontalScrollOffset) {
return;
}

this.hasPendingHorizontalScrollOffset = false;

afterNextRender(
() => {
const body = this.getBodyElement();

if (!body) {
return;
}

body.scrollLeft = this.horizontalScrollOffset;

// The header only follows a scroll event, scrolling to the same position does not raise one
body.dispatchEvent(new Event('scroll'));
},
{ injector: this.#injector },
);
}

ngOnDestroy(): void {
this.loadMoreSubscription.unsubscribe();
}
Expand Down
Loading