x - this.grid.pinnedRecordsCount);
}
- const result = DataUtil.merge(cloneArray(collection), colsToMerge, mergeStrategy, activeRowIndexes, this.grid);
+ activeRowIndexes = Array.from(new Set(activeRowIndexes)).filter(x => !isNaN(x));
+ const rootsToUpdate = [];
+ activeRowIndexes.forEach(index => {
+ const target = collection[index];
+ if (target) {
+ colsToMerge.forEach(col => {
+ const colMeta = target.cellMergeMeta.get(col.field);
+ const root = colMeta.root || (colMeta.rowSpan > 1 ? target : null);
+ if (root) {
+ rootsToUpdate.push(root);
+ }
+ });
+ }
+ });
+ const uniqueRoots = Array.from(new Set(rootsToUpdate));
+ if (uniqueRoots.length === 0) {
+ // if nothing to update, return
+ return collection;
+ }
+ let result = cloneArray(collection) as any;
+ uniqueRoots.forEach(x => {
+ const index = result.indexOf(x);
+ const colKeys = [...x.cellMergeMeta.keys()];
+ const cols = colsToMerge.filter(col => colKeys.indexOf(col.field) !== -1);
+ let res = [];
+ for (const col of cols) {
+
+ let childData = x.cellMergeMeta.get(col.field).childRecords;
+ const childRecs = childData.map(rec => rec.recordRef);
+ const isDate = col?.dataType === 'date' || col?.dataType === 'dateTime';
+ const isTime = col?.dataType === 'time' || col?.dataType === 'dateTime';
+ res = this.grid.mergeStrategy.merge(
+ [x.recordRef, ...childRecs],
+ col.field,
+ col.mergingComparer,
+ res,
+ activeRowIndexes.map(ri => ri - index),
+ isDate,
+ isTime,
+ this.grid);
+
+ }
+ result = result.slice(0, index).concat(res, result.slice(index + res.length));
+ });
+
+
return result;
}
}
diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html
index 276e89cb377..2b640e97ddd 100644
--- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html
+++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html
@@ -51,7 +51,8 @@
| gridRowPinning:id:true:pipeTrigger
| gridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:id:pipeTrigger:filteringPipeTrigger:true
| gridSort:sortingExpressions:[]:sortStrategy:id:pipeTrigger:true
- | gridCellMerge:columnsToMerge:cellMergeMode:mergeStrategy:activeRowIndexes:true:pipeTrigger; as pinnedData
+ | gridCellMerge:columnsToMerge:cellMergeMode:mergeStrategy:pipeTrigger
+ | gridUnmergeActive:columnsToMerge:activeRowIndexes:true:pipeTrigger; as pinnedData
) {
@if (pinnedData.length > 0) {
diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts
index 80c7c7c1f96..789b46b4453 100644
--- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts
+++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts
@@ -50,7 +50,7 @@ import { IgxGridValidationService } from '../grid/grid-validation.service';
import { IgxGridHierarchicalPipe, IgxGridHierarchicalPagingPipe } from './hierarchical-grid.pipes';
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
-import { IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe } from '../grid/grid.pipes';
+import { IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from '../grid/grid.pipes';
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
import { IgxRowEditTabStopDirective } from '../grid.rowEdit.directive';
import { IgxIconComponent } from '../../icon/icon.component';
@@ -353,7 +353,8 @@ export class IgxChildGridRowComponent implements AfterViewInit, OnInit {
IgxGridHierarchicalPagingPipe,
IgxStringReplacePipe,
IgxGridCellMergePipe,
- IgxScrollInertiaDirective
+ IgxScrollInertiaDirective,
+ IgxGridUnmergeActivePipe
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts
index 648f70ae17e..59277524c12 100644
--- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts
+++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts
@@ -198,6 +198,73 @@ describe('IgxTreeGrid - Integration #tGrid', () => {
treeGrid = fix.componentInstance.treeGrid;
});
+ it('should preserve the order of records on inner levels', () => {
+ fix = TestBed.createComponent(IgxTreeGridPrimaryForeignKeyComponent);
+ fix.componentInstance.sortByName = true;
+ fix.detectChanges();
+ treeGrid = fix.componentInstance.treeGrid;
+
+ const expectedFlatData = [
+ {
+ "ID": 1,
+ "ParentID": -1,
+ "Name": "Casey Houston",
+ "JobTitle": "Vice President",
+ "Age": 32
+ },
+ {
+ "ID": 2,
+ "ParentID": 1,
+ "Name": "Gilberto Todd",
+ "JobTitle": "Director",
+ "Age": 41
+ },
+ {
+ "ID": 7,
+ "ParentID": 2,
+ "Name": "Debra Morton",
+ "JobTitle": "Associate Software Developer",
+ "Age": 35
+ },
+ {
+ "ID": 3,
+ "ParentID": 2,
+ "Name": "Tanya Bennett",
+ "JobTitle": "Director",
+ "Age": 29
+ },
+ {
+ "ID": 4,
+ "ParentID": 1,
+ "Name": "Jack Simon",
+ "JobTitle": "Software Developer",
+ "Age": 33
+ },
+ {
+ "ID": 10,
+ "ParentID": -1,
+ "Name": "Eduardo Ramirez",
+ "JobTitle": "Manager",
+ "Age": 53
+ },
+ {
+ "ID": 9,
+ "ParentID": 10,
+ "Name": "Leslie Hansen",
+ "JobTitle": "Associate Software Developer",
+ "Age": 44
+ },
+ {
+ "ID": 6,
+ "ParentID": -1,
+ "Name": "Erma Walsh",
+ "JobTitle": "CEO",
+ "Age": 52
+ },
+ ]
+ expect(treeGrid.flatData).toEqual(expectedFlatData);
+ });
+
it('should transform a non-tree column into a tree column when pinning it', () => {
TreeGridFunctions.verifyTreeColumn(fix, 'ID', 5);
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html
index 33c79416332..7195fcbf71b 100644
--- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html
+++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html
@@ -53,7 +53,8 @@
| gridRowPinning:id:true:pipeTrigger
| treeGridFiltering:filteringExpressionsTree:filterStrategy:advancedFilteringExpressionsTree:pipeTrigger:filteringPipeTrigger:true
| treeGridSorting:sortingExpressions:treeGroupArea?.expressions:sortStrategy:pipeTrigger:true
- | gridCellMerge:columnsToMerge:cellMergeMode:mergeStrategy:activeRowIndexes:true:pipeTrigger; as pinnedData
+ | gridCellMerge:columnsToMerge:cellMergeMode:mergeStrategy:pipeTrigger
+ | gridUnmergeActive:columnsToMerge:activeRowIndexes:true:pipeTrigger; as pinnedData
) {
@if (pinnedData.length > 0) {
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts
index 4820bca8339..40196a62d0b 100644
--- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts
+++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts
@@ -82,7 +82,7 @@ import { IgxGridDragSelectDirective } from '../selection/drag-select.directive';
import { IgxGridBodyDirective } from '../grid.common';
import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component';
import { IgxTextHighlightService } from '../../directives/text-highlight/text-highlight.service';
-import { IgxGridCellMergePipe } from '../grid/grid.pipes';
+import { IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from '../grid/grid.pipes';
import { DefaultTreeGridMergeStrategy, IGridMergeStrategy } from '../../data-operations/merge-strategy';
import { IgxScrollInertiaDirective } from '../../directives/scroll-inertia/scroll_inertia.directive';
@@ -173,7 +173,8 @@ let NEXT_ID = 0;
IgxTreeGridAddRowPipe,
IgxStringReplacePipe,
IgxGridCellMergePipe,
- IgxScrollInertiaDirective
+ IgxScrollInertiaDirective,
+ IgxGridUnmergeActivePipe
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts
index 053208508c6..0d00e8f77d3 100644
--- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts
+++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.pipes.ts
@@ -53,30 +53,44 @@ export class IgxTreeGridHierarchizingPipe implements PipeTransform {
return primaryKey ? rowData[primaryKey] : rowData;
}
- private hierarchizeFlatData(collection: any[], primaryKey: string, foreignKey: string,
- map: Map
, flatData: any[]):
- ITreeGridRecord[] {
- const result: ITreeGridRecord[] = [];
- const missingParentRecords: ITreeGridRecord[] = [];
+ /**
+ * Converts a flat array of data into a hierarchical (tree) structure,
+ * preserving the original order of the records among siblings.
+ *
+ * It uses a two-pass approach:
+ * 1. Creates all ITreeGridRecord objects and populates the Map for quick lookup.
+ * 2. Links the records by iterating again, ensuring children are added to
+ * their parent's children array in the order they appeared in the
+ * original collection.
+ *
+ * @param collection The flat array of data to be hierarchized. This is the array whose order should be preserved.
+ * @param primaryKey The name of the property in the data objects that serves as the unique identifier (e.g., 'id').
+ * @param foreignKey The name of the property in the data objects that links to the parent's primary key (e.g., 'parentId').
+ * @param map A pre-existing Map object (key: primaryKey value, value: ITreeGridRecord) used to store and quickly look up all created records.
+ * @param flatData The original flat data array. Used for passing to the setIndentationLevels method (not directly used for hierarchy building).
+ * @returns An array of ITreeGridRecord objects representing the root nodes of the hierarchy, ordered as they appeared in the original collection.
+ */
+ private hierarchizeFlatData(
+ collection: any[],
+ primaryKey: string,
+ foreignKey: string,
+ map: Map,
+ flatData: any[]
+ ): ITreeGridRecord[] {
collection.forEach(row => {
const record: ITreeGridRecord = {
key: this.getRowID(primaryKey, row),
data: row,
children: []
};
- const parent = map.get(row[foreignKey]);
- if (parent) {
- record.parent = parent;
- parent.children.push(record);
- } else {
- missingParentRecords.push(record);
- }
-
map.set(row[primaryKey], record);
});
- missingParentRecords.forEach(record => {
- const parent = map.get(record.data[foreignKey]);
+ const result: ITreeGridRecord[] = [];
+ collection.forEach(row => {
+ const record: ITreeGridRecord = map.get(row[primaryKey])!;
+ const parent = map.get(row[foreignKey]);
+
if (parent) {
record.parent = parent;
parent.children.push(record);
diff --git a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts
index 38f0578a17e..bca74571c50 100644
--- a/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts
+++ b/projects/igniteui-angular/src/lib/query-builder/query-builder.component.spec.ts
@@ -1,5 +1,5 @@
import { waitForAsync, TestBed, ComponentFixture, fakeAsync, tick, flush } from '@angular/core/testing';
-import { FilteringExpressionsTree, FilteringLogic, IExpressionTree, IgxChipComponent, IgxComboComponent, IgxDateFilteringOperand, IgxNumberFilteringOperand, IgxQueryBuilderComponent, IgxQueryBuilderHeaderComponent, IgxQueryBuilderSearchValueTemplateDirective } from 'igniteui-angular';
+import { FilteringExpressionsTree, FilteringLogic, IExpressionTree, IgxChipComponent, IgxComboComponent, IgxDateFilteringOperand, IgxIconComponent, IgxInputGroupComponent, IgxNumberFilteringOperand, IgxQueryBuilderComponent, IgxQueryBuilderHeaderComponent, IgxQueryBuilderSearchValueTemplateDirective, IgxSelectComponent } from 'igniteui-angular';
import { Component, OnInit, ViewChild } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
@@ -837,8 +837,10 @@ describe('IgxQueryBuilder', () => {
QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 10); // Select 'In' operator.
// Verify operator icon
- // const operatorInputGroup = QueryBuilderFunctions.getQueryBuilderOperatorSelect(fix).querySelector('igx-input-group') as HTMLElement;
- // expect(operatorInputGroup.querySelector('igx-icon').attributes.getNamedItem('ng-reflect-name').nodeValue).toEqual('in');
+ const operatorSelectDebugElement = fix.debugElement.queryAll(By.directive(IgxSelectComponent))[2];
+ const inputDebugElement = operatorSelectDebugElement.query(By.directive(IgxInputGroupComponent));
+ const iconDebugElem = inputDebugElement.query(By.directive(IgxIconComponent));
+ expect(iconDebugElem.componentInstance.name).toEqual('in');
const input = QueryBuilderFunctions.getQueryBuilderValueInput(fix).querySelector('input');
// Verify value input placeholder
@@ -914,8 +916,10 @@ describe('IgxQueryBuilder', () => {
QueryBuilderFunctions.selectOperatorInEditModeExpression(fix, 11); // Select 'Not-In' operator.
// Verify operator icon
- // const operatorInputGroup = QueryBuilderFunctions.getQueryBuilderOperatorSelect(fix).querySelector('igx-input-group') as HTMLElement;
- // expect(operatorInputGroup.querySelector('igx-icon').attributes.getNamedItem('ng-reflect-name').nodeValue).toEqual('not-in');
+ const operatorSelectDebugElement = fix.debugElement.queryAll(By.directive(IgxSelectComponent))[2];
+ const inputDebugElement = operatorSelectDebugElement.query(By.directive(IgxInputGroupComponent));
+ const iconDebugElem = inputDebugElement.query(By.directive(IgxIconComponent));
+ expect(iconDebugElem.componentInstance.name).toEqual('not-in');
const input = QueryBuilderFunctions.getQueryBuilderValueInput(fix).querySelector('input');
// Verify value input placeholder
diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
index 37b8a3a7c66..0103dbe9702 100644
--- a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts
@@ -13,7 +13,7 @@ import { IgxColumnComponent } from '../grids/columns/column.component';
import { IgxFilteringOperand, IgxNumberFilteringOperand } from '../data-operations/filtering-condition';
import { IFilteringExpressionsTree, FilteringExpressionsTree } from '../data-operations/filtering-expressions-tree';
import { FilteringStrategy, IgxFilterItem } from '../data-operations/filtering-strategy';
-import { ColumnPinningPosition, ISortingOptions, IgxExcelStyleHeaderIconDirective, IgxGridToolbarAdvancedFilteringComponent, IgxSortAscendingHeaderIconDirective, IgxSortDescendingHeaderIconDirective, IgxSortHeaderIconDirective } from '../grids/public_api';
+import { ColumnPinningPosition, ISortingOptions, IgxExcelStyleConditionalFilterComponent, IgxExcelStyleHeaderIconDirective, IgxGridToolbarAdvancedFilteringComponent, IgxSortAscendingHeaderIconDirective, IgxSortDescendingHeaderIconDirective, IgxSortHeaderIconDirective } from '../grids/public_api';
import { IgxRowAddTextDirective, IgxRowEditActionsDirective, IgxRowEditTabStopDirective, IgxRowEditTemplateDirective, IgxRowEditTextDirective } from '../grids/grid.rowEdit.directive';
import { IgxExcelStyleColumnOperationsTemplateDirective, IgxExcelStyleFilterOperationsTemplateDirective, IgxGridExcelStyleFilteringComponent } from '../grids/filtering/excel-style/excel-style-filtering.component';
import { FilteringLogic } from '../data-operations/filtering-expression.interface';
@@ -2782,3 +2782,33 @@ export class ObjectCloneStrategy implements IDataCloneStrategy {
export class IgxGridRowEditingDefinedColumnsComponent extends BasicGridComponent {
public override data = SampleTestData.foodProductData();
}
+
+@Component({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ imports: [IgxGridComponent, IgxColumnComponent, IgxExcelStyleConditionalFilterComponent, IgxGridExcelStyleFilteringComponent, IgxExcelStyleFilterOperationsTemplateDirective]
+})
+export class IgxGridConditionalFilteringComponent extends IgxGridFilteringComponent {
+}
diff --git a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts
index bebe4b0955c..cf93eb7c80f 100644
--- a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts
@@ -28,6 +28,8 @@ export let gridsubscriptions: Subscription [] = [];
export const setupGridScrollDetection = (fixture: ComponentFixture, grid: GridType) => {
gridsubscriptions.push(grid.verticalScrollContainer.chunkLoad.subscribe(() => fixture.detectChanges()));
gridsubscriptions.push(grid.parentVirtDir.chunkLoad.subscribe(() => fixture.detectChanges()));
+ gridsubscriptions.push(grid.activeNodeChange.subscribe(() => grid.cdr.detectChanges()));
+ gridsubscriptions.push(grid.selected.subscribe(() => grid.cdr.detectChanges()));
};
export const setupHierarchicalGridScrollDetection = (fixture: ComponentFixture, hierarchicalGrid: IgxHierarchicalGridComponent) => {
diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts
index 122aaa8a80f..9d0706598a8 100644
--- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts
+++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-components.spec.ts
@@ -159,10 +159,17 @@ export class IgxTreeGridWithNoScrollsComponent {
`,
imports: [IgxTreeGridComponent, IgxColumnComponent, IgxPaginatorComponent]
})
-export class IgxTreeGridPrimaryForeignKeyComponent {
+export class IgxTreeGridPrimaryForeignKeyComponent implements OnInit {
@ViewChild(IgxTreeGridComponent, { static: true }) public treeGrid: IgxTreeGridComponent;
- public data = SampleTestData.employeePrimaryForeignKeyTreeData();
+ public data = [];
public paging = false;
+ public sortByName = false;
+
+ public ngOnInit(): void {
+ this.data = !this.sortByName
+ ? SampleTestData.employeePrimaryForeignKeyTreeData()
+ : SampleTestData.employeePrimaryForeignKeyTreeData().sort((a, b) => a.Name.localeCompare(b.Name));
+ }
}
@Component({
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.html b/src/app/grid-cellMerging/grid-cellMerging.component.html
index 73233eb9c1c..b578fb8c3ef 100644
--- a/src/app/grid-cellMerging/grid-cellMerging.component.html
+++ b/src/app/grid-cellMerging/grid-cellMerging.component.html
@@ -50,7 +50,7 @@ Grid with cell merge
Value: {{val}},Index: {{cell.row.index}}
-
+
@@ -70,8 +70,7 @@ Grid with cell merge
-
-
+
diff --git a/src/app/grid-cellMerging/grid-cellMerging.component.ts b/src/app/grid-cellMerging/grid-cellMerging.component.ts
index 3374c5e606f..19c7ed681e1 100644
--- a/src/app/grid-cellMerging/grid-cellMerging.component.ts
+++ b/src/app/grid-cellMerging/grid-cellMerging.component.ts
@@ -38,7 +38,7 @@ import { INVOICE_DATA } from '../shared/invoiceData';
FormsModule,
IgxColumnComponent,
IgxGridComponent,
- IgxPaginatorComponent,
+ // IgxPaginatorComponent,
IgxActionStripComponent,
IgxGridPinningActionsComponent,
IgxGridToolbarComponent,
@@ -66,6 +66,17 @@ export class GridCellMergingComponent {
@ViewChild('grid1', { static: true }) public grid: IgxGridComponent;
public data = INVOICE_DATA;
+ constructor(){
+ const allData = INVOICE_DATA
+ const length = INVOICE_DATA.length;
+ for (let i = 1; i <= 600_000; i++) {
+ const rnd = Math.floor(Math.random() * length);
+ allData.push(Object.assign({}, INVOICE_DATA[rnd]));
+ }
+
+ this.data = allData;
+ }
+
public toggleStrategy() {
if (this.treeGridMergeStrategy instanceof ByLevelTreeGridMergeStrategy) {
this.treeGridMergeStrategy = new DefaultTreeGridMergeStrategy();
diff --git a/src/app/grid-performance/grid-performance.sample.html b/src/app/grid-performance/grid-performance.sample.html
index 7e702eaa343..21792206a21 100644
--- a/src/app/grid-performance/grid-performance.sample.html
+++ b/src/app/grid-performance/grid-performance.sample.html
@@ -8,7 +8,7 @@ Fixed Size Rows
@for (c of columns; track c) {
-
+
}