Skip to content

Commit d4a214f

Browse files
fix(advance-filtering): handle error when columns are dynamically changed
1 parent e00da42 commit d4a214f

File tree

5 files changed

+171
-22
lines changed

5 files changed

+171
-22
lines changed

projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import {
1515
IgxGridAdvancedFilteringComponent,
1616
IgxGridExternalAdvancedFilteringComponent,
1717
IgxGridAdvancedFilteringBindingComponent,
18-
IgxGridAdvancedFilteringOverlaySettingsComponent
18+
IgxGridAdvancedFilteringOverlaySettingsComponent,
19+
IgxGridAdvancedFilteringDynamicColumnsComponent
1920
} from '../../test-utils/grid-samples.spec';
2021
import { ControlsFunction } from '../../test-utils/controls-functions.spec';
2122
import { FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy';
2223
import { IgxHierGridExternalAdvancedFilteringComponent } from '../../test-utils/hierarchical-grid-components.spec';
2324
import { IgxHierarchicalGridComponent } from '../hierarchical-grid/public_api';
2425
import { IFilteringEventArgs } from '../public_api';
26+
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
2527

2628
const ADVANCED_FILTERING_OPERATOR_LINE_AND_CSS_CLASS = 'igx-filter-tree__line--and';
2729
const ADVANCED_FILTERING_OPERATOR_LINE_OR_CSS_CLASS = 'igx-filter-tree__line--or';
@@ -38,7 +40,8 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => {
3840
IgxGridAdvancedFilteringComponent,
3941
IgxGridExternalAdvancedFilteringComponent,
4042
IgxGridAdvancedFilteringBindingComponent,
41-
IgxHierGridExternalAdvancedFilteringComponent
43+
IgxHierGridExternalAdvancedFilteringComponent,
44+
IgxGridAdvancedFilteringDynamicColumnsComponent
4245
]
4346
});
4447
}));
@@ -2048,6 +2051,84 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => {
20482051
expect(rows.length).toEqual(1, 'Wrong filtered rows count');
20492052
}));
20502053

2054+
it('should handle advanced filtering correctly when grid columns and data are dynamically changed', fakeAsync(() => {
2055+
const fixture = TestBed.createComponent(IgxGridAdvancedFilteringDynamicColumnsComponent);
2056+
grid = fixture.componentInstance.grid;
2057+
fixture.detectChanges();
2058+
2059+
grid.height = '800px';
2060+
fixture.detectChanges();
2061+
tick(50);
2062+
2063+
expect(grid.filteredData).toBeNull();
2064+
expect(grid.rowList.length).toBe(8);
2065+
expect(GridFunctions.getCurrentCellFromGrid(grid, 0, 1).value).toBe('Ignite UI for JavaScript');
2066+
expect(GridFunctions.getCurrentCellFromGrid(grid, 1, 1).value).toBe('NetAdvantage');
2067+
2068+
// Open Advanced Filtering dialog
2069+
GridFunctions.clickAdvancedFilteringButton(fixture);
2070+
fixture.detectChanges();
2071+
2072+
// Click the initial 'Add And Group' button
2073+
GridFunctions.clickAdvancedFilteringInitialAddGroupButton(fixture, 0);
2074+
tick(100);
2075+
fixture.detectChanges();
2076+
2077+
// Populate edit inputs
2078+
selectColumnInEditModeExpression(fixture, 1);
2079+
selectOperatorInEditModeExpression(fixture, 2);
2080+
const input = GridFunctions.getAdvancedFilteringValueInput(fixture).querySelector('input');
2081+
UIInteractions.clickAndSendInputElementValue(input, 'ign', fixture);
2082+
2083+
// Commit the populated expression
2084+
GridFunctions.clickAdvancedFilteringExpressionCommitButton(fixture);
2085+
fixture.detectChanges();
2086+
2087+
// Apply the filters
2088+
GridFunctions.clickAdvancedFilteringApplyButton(fixture);
2089+
fixture.detectChanges();
2090+
2091+
// Verify the filter results
2092+
expect(grid.filteredData.length).toEqual(2);
2093+
expect(grid.rowList.length).toBe(2);
2094+
expect(GridFunctions.getCurrentCellFromGrid(grid, 0, 1).value).toBe('Ignite UI for JavaScript');
2095+
expect(GridFunctions.getCurrentCellFromGrid(grid, 1, 1).value).toBe('Ignite UI for Angular');
2096+
2097+
// Change the grid's columns collection
2098+
fixture.componentInstance.columns = [
2099+
{ field: 'ID', header: 'ID', width: '200px', type: 'string' },
2100+
{ field: 'CompanyName', header: 'Company Name', width: '200px', type: 'string'},
2101+
{ field: 'ContactName', header: 'Contact Name', width: '200px', type: 'string' },
2102+
{ field: 'ContactTitle', header: 'Contact Title', width: '200px', type: 'string' },
2103+
{ field: 'City', header: 'City', width: '200px', type: 'string' },
2104+
{ field: 'Country', header: 'Country', width: '200px', type: 'string' },
2105+
];
2106+
fixture.detectChanges();
2107+
flush();
2108+
2109+
// Change the grid's data collection
2110+
setTimeout(() => {
2111+
grid.data = SampleTestData.contactInfoDataFull();
2112+
fixture.detectChanges();
2113+
flush();
2114+
});
2115+
2116+
// Check for error messages in the console
2117+
const consoleSpy = spyOn(console, 'error');
2118+
2119+
// Open Advanced Filtering dialog
2120+
GridFunctions.clickAdvancedFilteringButton(fixture);
2121+
fixture.detectChanges();
2122+
flush();
2123+
2124+
// Verify the filters are cleared
2125+
expect(grid.filteredData).toEqual([]);
2126+
expect(grid.rowList.length).toBe(0);
2127+
2128+
// Check for error messages in the console
2129+
expect(consoleSpy).not.toHaveBeenCalled();
2130+
}));
2131+
20512132
describe('Context Menu - ', () => {
20522133
it('Should discard added group when clicking its operator line without having a single expression.', fakeAsync(() => {
20532134
// Open Advanced Filtering dialog.

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar-advanced-filtering.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
<ng-content></ng-content>
88
</span>
99
<span *ngIf="!ref.childNodes.length">{{ grid?.resourceStrings.igx_grid_toolbar_advanced_filtering_button_label }}</span>
10-
<span class="igx-adv-filter--column-number" *ngIf="grid?.advancedFilteringExpressionsTree"> ({{ numberOfColumns }}) </span>
10+
<span class="igx-adv-filter--column-number" *ngIf="numberOfColumns > 0"> ({{ numberOfColumns }}) </span>
1111
</button>

projects/igniteui-angular/src/lib/grids/toolbar/grid-toolbar-advanced-filtering.component.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ export class IgxGridToolbarAdvancedFilteringComponent implements AfterViewInit {
4747
this.grid?.advancedFilteringExpressionsTreeChange.subscribe(filteringTree => {
4848
this.numberOfColumns = this.extractUniqueFieldNamesFromFilterTree(filteringTree).length;
4949
});
50+
this.grid?.filteringExpressionsTreeChange.subscribe(() => {
51+
setTimeout(() => {
52+
if (this.grid.advancedFilteringExpressionsTree) {
53+
const dataKeys = Object.keys(this.grid.data[0]);
54+
const filteringOperands = this.grid.advancedFilteringExpressionsTree.filteringOperands;
55+
56+
const allOperandsPresent = filteringOperands.every(operand => {
57+
if (operand instanceof FilteringExpressionsTree) {
58+
// Recursively check nested filtering trees
59+
return this.checkFilteringOperands(operand, dataKeys);
60+
} else {
61+
return dataKeys.includes(operand.fieldName);
62+
}
63+
});
64+
65+
if (!allOperandsPresent) {
66+
setTimeout(() => {
67+
this.grid.advancedFilteringExpressionsTree = this.grid.filteringExpressionsTree;
68+
this.numberOfColumns = 0;
69+
});
70+
}
71+
}
72+
});
73+
});
5074
}
5175

5276
/**
@@ -68,4 +92,15 @@ export class IgxGridToolbarAdvancedFilteringComponent implements AfterViewInit {
6892
});
6993
return [...new Set(columnNames)];
7094
}
95+
96+
private checkFilteringOperands(filteringTree: FilteringExpressionsTree, dataKeys: string[]): boolean {
97+
return filteringTree.filteringOperands.every(operand => {
98+
if (operand instanceof FilteringExpressionsTree) {
99+
// Recursively check nested filtering trees
100+
return this.checkFilteringOperands(operand, dataKeys);
101+
} else {
102+
return dataKeys.includes(operand.fieldName);
103+
}
104+
});
105+
}
71106
}

projects/igniteui-angular/src/lib/query-builder/query-builder.component.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -991,31 +991,33 @@ export class IgxQueryBuilderComponent extends DisplayDensityBase implements Afte
991991
this.currentGroup = groupItem;
992992
}
993993

994-
private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem): ExpressionGroupItem {
995-
let groupItem: ExpressionGroupItem;
996-
if (expressionTree) {
997-
groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
998-
999-
for (const expr of expressionTree.filteringOperands) {
1000-
if (expr instanceof FilteringExpressionsTree) {
1001-
groupItem.children.push(this.createExpressionGroupItem(expr, groupItem));
1002-
} else {
1003-
const filteringExpr = expr as IFilteringExpression;
1004-
const exprCopy: IFilteringExpression = {
1005-
fieldName: filteringExpr.fieldName,
1006-
condition: filteringExpr.condition,
1007-
searchVal: filteringExpr.searchVal,
1008-
ignoreCase: filteringExpr.ignoreCase
1009-
};
994+
private createExpressionGroupItem(expressionTree: IExpressionTree, parent?: ExpressionGroupItem): ExpressionGroupItem | null {
995+
if (!expressionTree) {
996+
return null;
997+
}
998+
999+
const groupItem = new ExpressionGroupItem(expressionTree.operator, parent);
1000+
1001+
for (const expr of expressionTree.filteringOperands) {
1002+
if (expr instanceof FilteringExpressionsTree) {
1003+
const childGroup = this.createExpressionGroupItem(expr, groupItem);
1004+
if (childGroup) {
1005+
groupItem.children.push(childGroup);
1006+
}
1007+
} else {
1008+
const filteringExpr = expr as IFilteringExpression;
1009+
const field = this.fields.find(el => el.field === filteringExpr.fieldName);
1010+
1011+
if (field) {
1012+
const exprCopy: IFilteringExpression = { ...filteringExpr };
10101013
const operandItem = new ExpressionOperandItem(exprCopy, groupItem);
1011-
const field = this.fields.find(el => el.field === filteringExpr.fieldName);
10121014
operandItem.fieldLabel = field.label || field.header || field.field;
10131015
groupItem.children.push(operandItem);
10141016
}
10151017
}
10161018
}
10171019

1018-
return groupItem;
1020+
return groupItem.children.length > 0 ? groupItem : null;
10191021
}
10201022

10211023
private createExpressionTreeFromGroupItem(groupItem: ExpressionGroupItem): FilteringExpressionsTree {

projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,37 @@ export class IgxGridAdvancedFilteringComponent extends BasicGridComponent {
11741174
}
11751175
}
11761176

1177+
@Component({
1178+
template: `<igx-grid [data]="data" height="500px" [allowAdvancedFiltering]="true">
1179+
<igx-grid-toolbar></igx-grid-toolbar>
1180+
<igx-column *ngFor="let c of columns"
1181+
[field]="c.field"
1182+
[header]="c.header"
1183+
[width]="c.width"
1184+
[filterable]="true"
1185+
[dataType]="c.type">
1186+
</igx-column>
1187+
</igx-grid>`,
1188+
standalone: true,
1189+
imports: [NgFor, IgxGridComponent, IgxColumnComponent, IgxGridToolbarComponent]
1190+
})
1191+
export class IgxGridAdvancedFilteringDynamicColumnsComponent extends BasicGridComponent {
1192+
public override data = [];
1193+
public columns = [];
1194+
1195+
public ngOnInit(): void {
1196+
this.columns = [
1197+
{ field: 'ID', header: 'HeaderID', width: '100px', type: 'number' },
1198+
{ field: 'ProductName', header: 'Product Name', width: '200px', type: 'string'},
1199+
{ field: 'Downloads', header: 'Downloads', width: '100px', type: 'number' },
1200+
{ field: 'Released', header: 'Released', width: '100px', type: 'boolean' },
1201+
{ field: 'ReleaseDate', header: 'Release Date', width: '200px', type: 'date' },
1202+
{ field: 'AnotherField', header: 'Another Field', width: '200px', type: 'string' },
1203+
];
1204+
this.data = SampleTestData.excelFilteringData();
1205+
}
1206+
}
1207+
11771208
@Component({
11781209
template: `<igx-grid [data]="data" height="500px" [allowAdvancedFiltering]="true">
11791210
<igx-grid-toolbar>
@@ -1738,7 +1769,7 @@ export class IgxGridGroupByComponent extends DataParent implements OnInit {
17381769
<igx-grid [data]="data">
17391770
<igx-column [editable]="true" field="fullName">
17401771
</igx-column>
1741-
<igx-column field="age" [editable]="true" [dataType]="'number'">
1772+
<igx-column field="age" [editable]="true" [dataType]="'number'">
17421773
</igx-column>
17431774
<igx-column field="isActive" [editable]="true" [dataType]="'boolean'"></igx-column>
17441775
<igx-column field="birthday" [editable]="true" [dataType]="'date'"></igx-column>

0 commit comments

Comments
 (0)