From 2989cb99df7b24cfc8c2302bee52cdb4d6bf33e8 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Mon, 28 Jul 2025 13:52:36 +0530 Subject: [PATCH] mgr/dashboard: fix table dom re-rendering each table refresh creates a new data or update the existing data. this causes the existing data to be completely replaced with a newer one and thereby loosing the trackBy functionality. So I am modifying the data in-place so that the memory reference doesn't get changed Fixes: https://tracker.ceph.com/issues/72491 Signed-off-by: Nizamudeen A --- .../shared/datatable/table/table.component.ts | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts index 787d041733124..d1aea3494fc36 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/datatable/table/table.component.ts @@ -376,6 +376,7 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr return filter.value !== undefined; }); } + private previousRows = new Map(); constructor( // private ngZone: NgZone, @@ -442,43 +443,59 @@ export class TableComponent implements AfterViewInit, OnInit, OnChanges, OnDestr .subscribe({ next: (values) => { const datasets: TableItem[][] = values.map((val) => { - return this.tableColumns.map((column: CdTableColumn, colIndex: number) => { + const rowId = val?.id ?? val?.[this.identifier]; + const prevRow = this.previousRows.get(rowId); + + const newRow: TableItem[] = this.tableColumns.map((column, colIndex) => { const rowValue = _.get(val, column?.prop); const pipeTransform = () => column?.prop ? column.pipe.transform(rowValue) : column.pipe.transform(val); - let tableItem = new TableItem({ - selected: val, - data: { - value: column.pipe ? pipeTransform() : rowValue, - row: val, - column: { ...column, ...val } - } - }); + let existingCell: TableItem | undefined = prevRow?.[colIndex]; + const oldValue = existingCell?.data?.value; - if (colIndex === 0) { - tableItem.data = { ...tableItem.data, row: val }; + const newValue = column.pipe ? pipeTransform() : rowValue; - if (this.hasDetails) { - tableItem.expandedData = val; - tableItem.expandedTemplate = this.rowDetailTpl; - } - } + if (existingCell && !_.isEqual(oldValue, newValue)) { + // here i am updating value in place + existingCell.data.value = newValue; + existingCell.data.row = val; + existingCell.data.column = { ...column, ...val }; - if (column.cellClass && _.isFunction(column.cellClass)) { - this.model.header[colIndex].className = column.cellClass({ - row: val, - column, - value: rowValue - }); + if (colIndex === 0 && this.hasDetails) { + existingCell.expandedData = val; + existingCell.expandedTemplate = this.rowDetailTpl; + } } - tableItem.template = column.cellTemplate || this.defaultValueTpl; - return tableItem; + return ( + existingCell ?? + new TableItem({ + selected: val, + data: { + value: newValue, + row: val, + column: { ...column, ...val } + }, + template: column.cellTemplate || this.defaultValueTpl, + ...(colIndex === 0 && this.hasDetails + ? { expandedData: val, expandedTemplate: this.rowDetailTpl } + : {}) + }) + ); }); + + this.previousRows.set(rowId, newRow); + return newRow; }); - if (!_.isEqual(this.model.data, datasets)) { + // Only update the data if actual row content changed + const prevRaw = this.model.data.map((row) => row?.[0]?.data?.row); + const newRaw = values; + + const dataChanged = !_.isEqual(prevRaw, newRaw); + + if (dataChanged || this.model.data.length !== datasets.length) { this.model.data = datasets; } } -- 2.39.5