Skip to content
Open
Show file tree
Hide file tree
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
Expand Up @@ -43,7 +43,7 @@
[externalPaging]="externalPaging()"
[rowHeight]="rowHeight()"
[rowCount]="rowCount()"
[offset]="offset"
[offset]="correctedOffset()"
[trackByProp]="trackByProp()"
[columns]="_internalColumns()"
[pageSize]="pageSize()"
Expand Down Expand Up @@ -97,7 +97,7 @@
[rowCount]="_internalGroupedRows() !== undefined ? _internalRows().length : rowCount()"
[groupCount]="_internalGroupedRows() !== undefined ? rowCount() : undefined"
[pageSize]="pageSize()"
[offset]="offset"
[offset]="correctedOffset()"
[footerHeight]="footerHeight()"
[footerTemplate]="_footer()"
[totalMessage]="messages().totalMessage ?? 'total'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,18 +327,18 @@ describe('DatatableComponent', () => {
component.columns = columns;
fixture.detectChanges();

const datatableComponent = fixture.debugElement.query(
const datatableComponent: DatatableComponent = fixture.debugElement.query(
By.directive(DatatableComponent)
).componentInstance;
datatableComponent.offset = 1;
datatableComponent.offset.set(1);

// sort by `id` descending
sortBy({ column: 1 }, fixture);
fixture.detectChanges();
sortBy({ column: 1 }, fixture);
fixture.detectChanges();

expect(datatableComponent.offset).toBe(0);
expect(datatableComponent.offset()).toBe(0);
});

it('should support array data', () => {
Expand Down
39 changes: 22 additions & 17 deletions projects/ngx-datatable/src/lib/components/datatable.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
HostListener,
inject,
input,
Input,
IterableDiffer,
IterableDiffers,
linkedSignal,
Expand Down Expand Up @@ -230,12 +229,7 @@ export class DatatableComponent<TRow extends Row = any>
* The current offset ( page - 1 ) shown.
* Default value: `0`
*/
@Input({ transform: numberAttribute }) set offset(val: number) {
this._offset = val;
}
get offset(): number {
return Math.max(Math.min(this._offset, Math.ceil(this.rowCount() / this.pageSize()) - 1), 0);
}
readonly offset = model<number>(0);

/**
* Show the linear loading bar.
Expand Down Expand Up @@ -667,7 +661,6 @@ export class DatatableComponent<TRow extends Row = any>
private readonly _rowDiffCount = signal(0);

_offsetX = 0;
_offset = 0;
readonly _internalRows = computed(() => {
this._rowDiffCount(); // to trigger recalculation when row differ detects a change
let rows = this.rows()?.slice() ?? [];
Expand Down Expand Up @@ -734,6 +727,18 @@ export class DatatableComponent<TRow extends Row = any>
)
)
);

/**
* Computed signal that returns the corrected offset value.
* It ensures the offset is within valid bounds based on rowCount and pageSize.
*/
readonly correctedOffset = computed(() => {
const offset = this.offset();
const rowCount = this.rowCount();
const pageSize = this.pageSize();
return Math.max(Math.min(offset, Math.ceil(rowCount / pageSize) - 1), 0);
});
Comment on lines +735 to +740

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The calculation for correctedOffset can result in NaN if pageSize is 0. This can happen if there are no rows and no limit is set. While this is handled by isNaN checks in other parts of the code, it's more robust to prevent NaN from being generated in the first place. Adding a guard for pageSize === 0 will make the logic clearer and safer.

  readonly correctedOffset = computed(() => {
    const offset = this.offset();
    const rowCount = this.rowCount();
    const pageSize = this.pageSize();
    if (pageSize === 0) {
      return 0;
    }
    return Math.max(Math.min(offset, Math.ceil(rowCount / pageSize) - 1), 0);
  });

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fh1ch this is actually a very valid concern, but I think the proper fix is, to ensure pageSize >= 1 is always true.

I would do this in a follow-up


_subscriptions: Subscription[] = [];
_defaultColumnWidth = this.configuration?.defaultColumnWidth ?? 150;
/**
Expand Down Expand Up @@ -952,14 +957,14 @@ export class DatatableComponent<TRow extends Row = any>
return;
}

this.offset = offset;
this.offset.set(offset);

if (!isNaN(this.offset)) {
if (!isNaN(this.correctedOffset())) {
this.page.emit({
count: this.count(),
pageSize: this.pageSize(),
limit: this.limit(),
offset: this.offset,
offset: this.correctedOffset(),
sorts: this.sorts()
});
}
Expand All @@ -977,14 +982,14 @@ export class DatatableComponent<TRow extends Row = any>
* The footer triggered a page event.
*/
onFooterPage(event: PagerPageEvent) {
this.offset = event.page - 1;
this._bodyComponent().updateOffsetY(this.offset);
this.offset.set(event.page - 1);
this._bodyComponent().updateOffsetY(this.correctedOffset());

this.page.emit({
count: this.count(),
pageSize: this.pageSize(),
limit: this.limit(),
offset: this.offset,
offset: this.correctedOffset(),
sorts: this.sorts()
});

Expand Down Expand Up @@ -1132,14 +1137,14 @@ export class DatatableComponent<TRow extends Row = any>
this.sorts.set(event.sorts);

// Always go to first page when sorting to see the newly sorted data
this.offset = 0;
this._bodyComponent().updateOffsetY(this.offset);
this.offset.set(0);
this._bodyComponent().updateOffsetY(this.correctedOffset());
// Emit the page object with updated offset value
this.page.emit({
count: this.count(),
pageSize: this.pageSize(),
limit: this.limit(),
offset: this.offset,
offset: this.correctedOffset(),
sorts: this.sorts()
});
this.sort.emit(event);
Expand Down
2 changes: 1 addition & 1 deletion src/app/basic/filter.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ export class FilterComponent {
return d.name.toLowerCase().includes(val) || !val;
});
// Whenever the filter changes, always go back to the first page
this.table.offset = 0;
this.table.offset.set(0);
}
}
Loading