diff --git a/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter-grid.spec.ts b/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter-grid.spec.ts index 45d15167a3d..44f66555ff9 100644 --- a/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter-grid.spec.ts +++ b/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter-grid.spec.ts @@ -34,7 +34,9 @@ import { IgxHierarchicalGridExportComponent, IgxHierarchicalGridMCHCollapsibleComponent, IgxHierarchicalGridMultiColumnHeaderIslandsExportComponent, IgxHierarchicalGridMultiColumnHeadersExportComponent, - IgxHierarchicalGridSummariesExportComponent + IgxHierarchicalGridSummariesExportComponent, + IgxHierarchicalGridEmptyDataExportComponent, + IgxHierarchicalGridMissingChildDataExportComponent } from '../../../../../test-utils/hierarchical-grid-components.spec'; import { GridFunctions } from '../../../../../test-utils/grid-functions.spec'; import { IgxPivotGridMultipleRowComponent, IgxPivotGridTestComplexHierarchyComponent, SALES_DATA } from '../../../../../test-utils/pivot-grid-samples.spec'; @@ -75,6 +77,8 @@ describe('Excel Exporter', () => { IgxPivotGridTestComplexHierarchyComponent, IgxTreeGridSummariesKeyComponent, IgxHierarchicalGridSummariesExportComponent, + IgxHierarchicalGridEmptyDataExportComponent, + IgxHierarchicalGridMissingChildDataExportComponent, GroupedGridWithSummariesComponent, GridCurrencySummariesComponent, GridUserMeetingDataComponent, @@ -931,6 +935,41 @@ describe('Excel Exporter', () => { await exportAndVerify(hGrid, options, actualData.exportHierarchicalDataWithSkippedRows); }); + + it('should export hierarchical grid with empty data without throwing error', async () => { + fix = TestBed.createComponent(IgxHierarchicalGridEmptyDataExportComponent); + fix.detectChanges(); + + hGrid = fix.componentInstance.hGrid; + options = createExportOptions('HierarchicalGridEmptyDataExcelExport'); + + await expectAsync(getExportedData(hGrid, options)).toBeResolved(); + }); + + it('should export hierarchical grid with empty data and summaries without throwing error', async () => { + fix = TestBed.createComponent(IgxHierarchicalGridEmptyDataExportComponent); + fix.detectChanges(); + + hGrid = fix.componentInstance.hGrid; + const artistColumn = hGrid.columns.find(col => col.field === 'Artist'); + artistColumn.hasSummary = true; + fix.detectChanges(); + + options = createExportOptions('HierarchicalGridEmptyDataWithSummariesExcelExport'); + options.exportSummaries = true; + + await expectAsync(getExportedData(hGrid, options)).toBeResolved(); + }); + + it('should export hierarchical grid with missing child data key without throwing error', async () => { + fix = TestBed.createComponent(IgxHierarchicalGridMissingChildDataExportComponent); + fix.detectChanges(); + + hGrid = fix.componentInstance.hGrid; + options = createExportOptions('HierarchicalGridMissingChildDataExcelExport'); + + await expectAsync(getExportedData(hGrid, options)).toBeResolved(); + }); }); describe('', () => { diff --git a/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter.ts b/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter.ts index 00824131b91..8061064328a 100644 --- a/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter.ts +++ b/projects/igniteui-angular/grids/core/src/services/excel/excel-exporter.ts @@ -77,6 +77,10 @@ export class IgxExcelExporterService extends IgxBaseExporter { const firstDataElement = data[0]; const isHierarchicalGrid = firstDataElement?.type === ExportRecordType.HierarchicalGridRecord; const isPivotGrid = firstDataElement?.type === ExportRecordType.PivotGridRecord; + const ownersKeys = Array.from(this._ownersMap.keys()); + const firstKey = ownersKeys[0]; + const isHierarchicalGridByMap = firstKey && typeof firstKey !== 'string'; + const filterColumns = (columns) => columns.filter(col => col.field !== GRID_LEVEL_COL && !col.skip && col.headerType === ExportHeaderType.ColumnHeader); let rootKeys; let columnCount; @@ -113,20 +117,30 @@ export class IgxExcelExporterService extends IgxBaseExporter { rootKeys = this._ownersMap.get(firstDataElement.owner).columns.filter(c => !c.skip).map(c => c.field); defaultOwner = this._ownersMap.get(firstDataElement.owner); } else { - defaultOwner = this._ownersMap.get(DEFAULT_OWNER); - const columns = defaultOwner.columns.filter(col => col.field !== GRID_LEVEL_COL && !col.skip && col.headerType === ExportHeaderType.ColumnHeader); - - columnWidths = defaultOwner.columnWidths; - indexOfLastPinnedColumn = defaultOwner.indexOfLastPinnedColumn; - columnCount = isPivotGrid ? columns.length + this.pivotGridFilterFieldsCount : columns.length; - rootKeys = columns.map(c => c.field); + // Check if this is actually a hierarchical grid (when data only contains summary records) + defaultOwner = isHierarchicalGridByMap + ? this._ownersMap.get(firstKey) + : this._ownersMap.get(DEFAULT_OWNER) || this._ownersMap.get(firstKey); + + if (defaultOwner) { + const columns = filterColumns(defaultOwner.columns); + + columnWidths = defaultOwner.columnWidths; + indexOfLastPinnedColumn = defaultOwner.indexOfLastPinnedColumn; + columnCount = isPivotGrid ? columns.length + this.pivotGridFilterFieldsCount : columns.length; + rootKeys = columns.map(c => c.field); + } } } else { - const ownersKeys = Array.from(this._ownersMap.keys()); + // For hierarchical grids with empty data, use the grid instance; otherwise try DEFAULT_OWNER first + defaultOwner = isHierarchicalGridByMap + ? this._ownersMap.get(firstKey) + : this._ownersMap.get(DEFAULT_OWNER) || this._ownersMap.get(firstKey); - defaultOwner = this._ownersMap.get(ownersKeys[0]); - columnWidths = defaultOwner.columnWidths; - columnCount = defaultOwner.columns.filter(col => col.field !== GRID_LEVEL_COL && !col.skip && col.headerType === ExportHeaderType.ColumnHeader).length; + if (defaultOwner) { + columnWidths = defaultOwner.columnWidths; + columnCount = filterColumns(defaultOwner.columns).length; + } } const worksheetData = diff --git a/projects/igniteui-angular/grids/core/src/services/excel/excel-files.ts b/projects/igniteui-angular/grids/core/src/services/excel/excel-files.ts index 0d5d81882c7..58940f0d587 100644 --- a/projects/igniteui-angular/grids/core/src/services/excel/excel-files.ts +++ b/projects/igniteui-angular/grids/core/src/services/excel/excel-files.ts @@ -551,9 +551,15 @@ export class WorksheetFile implements IExcelFile { } private getSummaryFunction(type: string, key: string, dimensionMapKey: any, recordLevel: number, col: IColumnInfo): string { - const dimensionMap = dimensionMapKey ? this.hierarchicalDimensionMap.get(dimensionMapKey) : this.dimensionMap; + const dimensionMap = dimensionMapKey ? (this.hierarchicalDimensionMap.get(dimensionMapKey) ?? this.dimensionMap) : this.dimensionMap; + if (!dimensionMap) { + return ''; + } const dimensions = dimensionMap.get(key); const levelDimensions = dimensionMap.get(GRID_LEVEL_COL); + if (!dimensions || !levelDimensions) { + return ''; + } let func = ''; let funcType = ''; diff --git a/projects/igniteui-angular/grids/core/src/services/exporter-common/base-export-service.ts b/projects/igniteui-angular/grids/core/src/services/exporter-common/base-export-service.ts index 91ad1ffafff..4e120508d3c 100644 --- a/projects/igniteui-angular/grids/core/src/services/exporter-common/base-export-service.ts +++ b/projects/igniteui-angular/grids/core/src/services/exporter-common/base-export-service.ts @@ -247,7 +247,8 @@ export abstract class IgxBaseExporter { const childLayoutList = grid.childLayoutList; for (const island of childLayoutList) { - this.mapHierarchicalGridColumns(island, grid.data[0]); + const gridData = grid.data && grid.data.length > 0 ? grid.data[0] : {}; + this.mapHierarchicalGridColumns(island, gridData); } } else if (grid.type === 'pivot') { this.pivotGridColumns = []; @@ -1212,22 +1213,27 @@ export abstract class IgxBaseExporter { let keyData; if (island.autoGenerate) { - keyData = gridData[island.key]; - const islandKeys = island.children.map(i => i.key); + keyData = gridData && gridData[island.key] ? gridData[island.key] : undefined; + const islandKeys = island.children && island.children.length > 0 ? island.children.map(i => i.key) : []; - const islandData = keyData.map(i => { - const newItem = {}; + if (keyData && Array.isArray(keyData) && keyData.length > 0) { + const islandData = keyData.map(i => { + const newItem = {}; - Object.keys(i).map(k => { - if (!islandKeys.includes(k)) { - newItem[k] = i[k]; - } - }); + Object.keys(i).map(k => { + if (!islandKeys.includes(k)) { + newItem[k] = i[k]; + } + }); - return newItem; - }); + return newItem; + }); - columnList = this.getAutoGeneratedColumns(islandData); + columnList = this.getAutoGeneratedColumns(islandData); + } else { + // If no data available, create empty column list + columnList = this.getAutoGeneratedColumns([{}]); + } } else { const islandColumnList = island.columns; columnList = this.getColumns(islandColumnList); @@ -1235,9 +1241,9 @@ export abstract class IgxBaseExporter { this._ownersMap.set(island, columnList); - if (island.children.length > 0) { + if (island.children && island.children.length > 0) { for (const childIsland of island.children) { - const islandKeyData = keyData !== undefined ? keyData[0] : {}; + const islandKeyData = keyData && Array.isArray(keyData) && keyData.length > 0 ? keyData[0] : {}; this.mapHierarchicalGridColumns(childIsland, islandKeyData); } } diff --git a/projects/igniteui-angular/test-utils/hierarchical-grid-components.spec.ts b/projects/igniteui-angular/test-utils/hierarchical-grid-components.spec.ts index 081cd33f2a8..1e3bbef5bc5 100644 --- a/projects/igniteui-angular/test-utils/hierarchical-grid-components.spec.ts +++ b/projects/igniteui-angular/test-utils/hierarchical-grid-components.spec.ts @@ -748,3 +748,44 @@ export class IgxHierarchicalGridDefaultComponent { this.data = SampleTestData.hierarchicalGridSingersFullData(); } } + +@Component({ + template: ` + + + + + + + + + + `, + imports: [IgxHierarchicalGridComponent, IgxColumnComponent, IgxRowIslandComponent] +}) +export class IgxHierarchicalGridEmptyDataExportComponent { + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hGrid: IgxHierarchicalGridComponent; + public data = []; +} + +@Component({ + template: ` + + + + + + + + + + `, + imports: [IgxHierarchicalGridComponent, IgxColumnComponent, IgxRowIslandComponent] +}) +export class IgxHierarchicalGridMissingChildDataExportComponent { + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hGrid: IgxHierarchicalGridComponent; + // Data without the 'Albums' key that the row island expects + public data = [ + { Artist: 'Artist1', Debut: 2000, GrammyNominations: 5, GrammyAwards: 2 } + ]; +}