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
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) {
/*
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