Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
4 changes: 3 additions & 1 deletion packages/angular-table/tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"allowJs": true,
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "Bundler"
"moduleResolution": "Bundler",
// Use a more recent lib to support ES2022 features, because we now use the index.ts as entrypoint for table-core
"lib": ["dom", "es2022"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
Expand Down
12 changes: 6 additions & 6 deletions packages/table-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
"datagrid"
],
"type": "commonjs",
"module": "build/lib/index.esm.js",
"main": "build/lib/index.js",
"types": "build/lib/index.d.ts",
"module": "src/index.ts",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": {
"types": "./build/lib/index.d.ts",
"import": "./build/lib/index.mjs",
"default": "./build/lib/index.js"
"types": "./src/index.ts",
"import": "./src/index.ts",
"default": "./src/index.ts"
},
"./package.json": "./package.json"
},
Expand Down
171 changes: 118 additions & 53 deletions packages/table-core/src/core/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface CoreRow<TData extends RowData> {
_getAllCellsByColumnId: () => Record<string, Cell<TData, unknown>>
_uniqueValuesCache: Record<string, unknown>
_valuesCache: Record<string, unknown>
clone: () => Row<TData>
/**
* The depth of the row (if nested or grouped) relative to the root row array.
* @link [API Docs](https://tanstack.com/table/v8/docs/api/core/row#depth)
Expand Down Expand Up @@ -92,44 +93,61 @@ export interface CoreRow<TData extends RowData> {
subRows: Row<TData>[]
}

export const createRow = <TData extends RowData>(
table: Table<TData>,
id: string,
original: TData,
rowIndex: number,
depth: number,
subRows?: Row<TData>[],
parentId?: string
): Row<TData> => {
let row: CoreRow<TData> = {
id,
index: rowIndex,
original,
depth,
parentId,
_valuesCache: {},
_uniqueValuesCache: {},
getValue: columnId => {
if (row._valuesCache.hasOwnProperty(columnId)) {
return row._valuesCache[columnId]
const rowProtosByTable = new WeakMap<Table<any>, any>()

/**
* Creates a table-specific row prototype object to hold shared row methods, including from all the
* features that have been registered on the table.
*/
export function getRowProto<TData extends RowData>(table: Table<TData>) {
let rowProto = rowProtosByTable.get(table)

if (!rowProto) {
const proto = {} as CoreRow<TData>

proto.clone = function () {
return Object.assign(Object.create(Object.getPrototypeOf(this)), this)
}

// Make the default fallback value available on the proto itself to avoid duplicating it on every row instance
// even if it's not used. This is safe as long as we don't mutate the value directly.
proto.subRows = [] as const

proto.getValue = function (columnId: string) {
/*

@dvmoritzschoefl dvmoritzschoefl Jun 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this creates a performance problem for sorting

The sorting fn will do smth like this:

  const sortingProps: Partial<NumberColumnDef<Data>> = {
    sortingFn: (rowA, rowB, columnId) => {
      const a = rowA.getValue<number | [number] | undefined>(columnId);
      const b = rowB.getValue<number | [number] | undefined>(columnId);

      const aValue = Array.isArray(a) ? a[0] : a;
      const bValue = Array.isArray(b) ? b[0] : b;

if (this._valuesCache.hasOwnProperty(columnId)) {
return this._valuesCache[columnId]
}
*/

const column = table.getColumn(columnId)

if (!column?.accessorFn) {
return undefined
}

row._valuesCache[columnId] = column.accessorFn(
row.original as TData,
rowIndex
return column.accessorFn(
this.original as TData,
this.index
) as any
/*
this._valuesCache[columnId] = column.accessorFn(
this.original as TData,
this.index
)

return row._valuesCache[columnId] as any
},
getUniqueValues: columnId => {
if (row._uniqueValuesCache.hasOwnProperty(columnId)) {
return row._uniqueValuesCache[columnId]
return this._valuesCache[columnId] as any
*/
}

proto.getUniqueValues = function (columnId: string) {
if (!this.hasOwnProperty('_uniqueValuesCache')) {
// lazy-init cache on the instance
this._uniqueValuesCache = {}
}

if (this._uniqueValuesCache.hasOwnProperty(columnId)) {
return this._uniqueValuesCache[columnId]
}

const column = table.getColumn(columnId)
Expand All @@ -139,57 +157,104 @@ export const createRow = <TData extends RowData>(
}

if (!column.columnDef.getUniqueValues) {
row._uniqueValuesCache[columnId] = [row.getValue(columnId)]
return row._uniqueValuesCache[columnId]
// Avoid unnecessary caching for unique values
return [this.getValue(columnId)];
// this._uniqueValuesCache[columnId] = [this.getValue(columnId)]
// return this._uniqueValuesCache[columnId]
}

row._uniqueValuesCache[columnId] = column.columnDef.getUniqueValues(
row.original as TData,
rowIndex
this._uniqueValuesCache[columnId] = column.columnDef.getUniqueValues(
this.original as TData,
this.index
)

return row._uniqueValuesCache[columnId] as any
},
renderValue: columnId =>
row.getValue(columnId) ?? table.options.renderFallbackValue,
subRows: subRows ?? [],
getLeafRows: () => flattenBy(row.subRows, d => d.subRows),
getParentRow: () =>
row.parentId ? table.getRow(row.parentId, true) : undefined,
getParentRows: () => {
return this._uniqueValuesCache[columnId] as any
}

proto.renderValue = function (columnId: string) {
return this.getValue(columnId) ?? table.options.renderFallbackValue
}

proto.getLeafRows = function () {
return flattenBy(this.subRows, d => d.subRows)
}

proto.getParentRow = function () {
return this.parentId ? table.getRow(this.parentId, true) : undefined
}

proto.getParentRows = function () {
let parentRows: Row<TData>[] = []
let currentRow = row
let currentRow = this
while (true) {
const parentRow = currentRow.getParentRow()
if (!parentRow) break
parentRows.push(parentRow)
currentRow = parentRow
}
return parentRows.reverse()
},
getAllCells: memo(
() => [table.getAllLeafColumns()],
leafColumns => {
}

proto.getAllCells = memo(
function (this: Row<TData>) {
return [this, table.getAllLeafColumns()]
},
(row, leafColumns) => {
return leafColumns.map(column => {
return createCell(table, row as Row<TData>, column, column.id)
return createCell(table, row, column, column.id)
})
},
getMemoOptions(table.options, 'debugRows', 'getAllCells')
),
)

_getAllCellsByColumnId: memo(
() => [row.getAllCells()],
allCells => {
proto._getAllCellsByColumnId = memo(
function (this: Row<TData>) {
// return [this.getAllCells()]
return []
},
() => {
throw new Error('Row._getAllCellsByColumnId not implemented because it is a memory leak')
/*
return allCells.reduce(
(acc, cell) => {
acc[cell.column.id] = cell
return acc
},
{} as Record<string, Cell<TData, unknown>>
)
*/
},
getMemoOptions(table.options, 'debugRows', 'getAllCellsByColumnId')
),
)

rowProtosByTable.set(table, proto)
rowProto = proto
}

return rowProto as CoreRow<TData>
}

export const createRow = <TData extends RowData>(
table: Table<TData>,
id: string,
original: TData,
rowIndex: number,
depth: number,
subRows?: Row<TData>[],
parentId?: string
): Row<TData> => {
const row: CoreRow<TData> = Object.create(getRowProto(table))
Object.assign(row, {
id,
index: rowIndex,
original,
depth,
parentId,
// _valuesCache: {},
})

if (subRows) {
row.subRows = subRows
}

for (let i = 0; i < table._features.length; i++) {
Expand Down
32 changes: 23 additions & 9 deletions packages/table-core/src/features/ColumnFiltering.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RowModel } from '..'
import { getRowProto, RowModel } from '..'
import { BuiltInFilterFn, filterFns } from '../filterFns'
import {
Column,
Expand Down Expand Up @@ -362,14 +362,6 @@ export const ColumnFiltering: TableFeature = {
}
},

createRow: <TData extends RowData>(
row: Row<TData>,
_table: Table<TData>
): void => {
row.columnFilters = {}
row.columnFiltersMeta = {}
},

createTable: <TData extends RowData>(table: Table<TData>): void => {
table.setColumnFilters = (updater: Updater<ColumnFiltersState>) => {
const leafColumns = table.getAllLeafColumns()
Expand Down Expand Up @@ -411,6 +403,28 @@ export const ColumnFiltering: TableFeature = {

return table._getFilteredRowModel()
}

// Lazy-init the backing caches on the instance so we don't take up memory for rows that don't need it
Object.defineProperties(getRowProto(table), {
columnFilters: {
get() {
return (this._columnFilters ??= {})
},
set(value) {
this._columnFilters = value
},
enumerable: true,
},
columnFiltersMeta: {
get() {
return (this._columnFiltersMeta ??= {})
},
set(value) {
this._columnFiltersMeta = value
},
enumerable: true,
},
})
},
}

Expand Down
48 changes: 27 additions & 21 deletions packages/table-core/src/features/ColumnGrouping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RowModel } from '..'
import { getRowProto, RowModel } from '..'
import { BuiltInAggregationFn, aggregationFns } from '../aggregationFns'
import {
AggregationFns,
Expand Down Expand Up @@ -353,31 +353,37 @@ export const ColumnGrouping: TableFeature = {

return table._getGroupedRowModel()
}
},

createRow: <TData extends RowData>(
row: Row<TData>,
table: Table<TData>
): void => {
row.getIsGrouped = () => !!row.groupingColumnId
row.getGroupingValue = columnId => {
if (row._groupingValuesCache.hasOwnProperty(columnId)) {
return row._groupingValuesCache[columnId]
}
Object.defineProperty(getRowProto(table), '_groupingValuesCache', {
get() {
// Lazy-init the backing cache on the instance so we don't take up memory for rows that don't need it
return (this.__groupingValuesCache ??= {})
},
enumerable: true,
})

Object.assign(getRowProto(table), {
getIsGrouped() {
return !!this.groupingColumnId
},
getGroupingValue(columnId) {
if (this._groupingValuesCache.hasOwnProperty(columnId)) {
return this._groupingValuesCache[columnId]
}

const column = table.getColumn(columnId)
const column = table.getColumn(columnId)

if (!column?.columnDef.getGroupingValue) {
return row.getValue(columnId)
}
if (!column?.columnDef.getGroupingValue) {
return this.getValue(columnId)
}

row._groupingValuesCache[columnId] = column.columnDef.getGroupingValue(
row.original
)
this._groupingValuesCache[columnId] = column.columnDef.getGroupingValue(
this.original
)

return row._groupingValuesCache[columnId]
}
row._groupingValuesCache = {}
return this._groupingValuesCache[columnId]
},
} as GroupingRow & Row<any>)
},

createCell: <TData extends RowData, TValue>(
Expand Down
Loading