diff --git a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts index 02a8c260a46..c9543362999 100644 --- a/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts +++ b/projects/igniteui-angular/src/lib/data-operations/expressions-tree-util.ts @@ -150,7 +150,7 @@ function recreateExpression(expression: IFilteringExpression, fields: FieldType[ } if (!expression.condition && expression.conditionName) { - throw Error('Wrong `conditionName`, `condition` or `field` provided!'); + throw Error('Wrong `conditionName`, `condition` or `field` provided! It is possible that there is a type mismatch between the condition type and field type.'); } if (!expression.conditionName) { diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts index 97d9b870c99..edf766bffcc 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-condition.ts @@ -908,7 +908,7 @@ export interface IFilteringOperation { hidden?: boolean; /* blazorCSSuppress */ /* blazorAlternateType: FilteringOperationLogicHandler */ - logic: (value: any, searchVal?: any, ignoreCase?: boolean) => boolean; + logic?: null | ((value: any, searchVal?: any, ignoreCase?: boolean) => boolean); } /** diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-expression.interface.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-expression.interface.ts index 3e308e0fe1c..dfb3212e810 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-expression.interface.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-expression.interface.ts @@ -14,9 +14,9 @@ export enum FilteringLogic { */ export declare interface IFilteringExpression { fieldName: string; - condition?: IFilteringOperation; - conditionName?: string; + condition?: IFilteringOperation | null; + conditionName?: string | null; searchVal?: any; - searchTree?: IExpressionTree; + searchTree?: IExpressionTree | null; ignoreCase?: boolean; } diff --git a/projects/igniteui-angular/src/lib/data-operations/filtering-expressions-tree.ts b/projects/igniteui-angular/src/lib/data-operations/filtering-expressions-tree.ts index 1381263f5bf..229050379b4 100644 --- a/projects/igniteui-angular/src/lib/data-operations/filtering-expressions-tree.ts +++ b/projects/igniteui-angular/src/lib/data-operations/filtering-expressions-tree.ts @@ -11,9 +11,9 @@ export enum FilteringExpressionsTreeType { export declare interface IExpressionTree { filteringOperands: (IExpressionTree | IFilteringExpression)[]; operator: FilteringLogic; - fieldName?: string; - entity?: string; - returnFields?: string[]; + fieldName?: string | null; + entity?: string | null; + returnFields?: string[] | null; } /* marshalByValue */ diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts index aa442dff801..0b346c079b5 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.directive.ts @@ -1856,8 +1856,8 @@ export abstract class IgxGridBaseDirective implements GridType, } value.type = FilteringExpressionsTreeType.Regular; - if (value && this.columns) { - this._filteringExpressionsTree = recreateTreeFromFields(value, this.columns) as IFilteringExpressionsTree; + if (value && this._columns?.length > 0) { + this._filteringExpressionsTree = recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; } else { this._filteringExpressionsTree = value; } @@ -1906,7 +1906,11 @@ export abstract class IgxGridBaseDirective implements GridType, if (value && isTree(value)) { value.type = FilteringExpressionsTreeType.Advanced; - this._advancedFilteringExpressionsTree = recreateTreeFromFields(value, this.columns) as IFilteringExpressionsTree; + if (this._columns && this._columns.length > 0) { + this._advancedFilteringExpressionsTree = recreateTreeFromFields(value, this._columns) as IFilteringExpressionsTree; + } else { + this._advancedFilteringExpressionsTree = value; + } this.filteringPipeTrigger++; } else { this._advancedFilteringExpressionsTree = null; @@ -6607,6 +6611,9 @@ export abstract class IgxGridBaseDirective implements GridType, if (this._columns && this._filteringExpressionsTree) { this._filteringExpressionsTree = recreateTreeFromFields(this._filteringExpressionsTree, this.columns) as IFilteringExpressionsTree; } + if (this._columns && this._advancedFilteringExpressionsTree) { + this._advancedFilteringExpressionsTree = recreateTreeFromFields(this._advancedFilteringExpressionsTree, this.columns) as IFilteringExpressionsTree; + } this.resetCaches(); } @@ -6668,9 +6675,12 @@ export abstract class IgxGridBaseDirective implements GridType, this.autogenerateColumns(); } else { this._columns = this.getColumnList(); - if (this._columns && this._filteringExpressionsTree) { - this._filteringExpressionsTree = recreateTreeFromFields(this._filteringExpressionsTree, this._columns) as IFilteringExpressionsTree; - } + } + if (this._columns && this._filteringExpressionsTree) { + this._filteringExpressionsTree = recreateTreeFromFields(this._filteringExpressionsTree, this._columns) as IFilteringExpressionsTree; + } + if (this._columns && this._advancedFilteringExpressionsTree) { + this._advancedFilteringExpressionsTree = recreateTreeFromFields(this._advancedFilteringExpressionsTree, this._columns) as IFilteringExpressionsTree; } this.initColumns(this._columns, (col: IgxColumnComponent) => this.columnInit.emit(col)); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts index aaed44fe8c8..9fb2b661cfe 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-advanced.spec.ts @@ -16,6 +16,7 @@ import { IgxGridExternalAdvancedFilteringComponent, IgxGridAdvancedFilteringBindingComponent, IgxGridAdvancedFilteringDynamicColumnsComponent, + IgxGridAdvancedFilteringSerializedTreeComponent, IgxGridAdvancedFilteringWithToolbarComponent } from '../../test-utils/grid-samples.spec'; import { FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy'; @@ -1450,6 +1451,52 @@ describe('IgxGrid - Advanced Filtering #grid - ', () => { // Verify no filtered data expect(grid.filteredData).toBe(null); })); + + }); + + describe('Expression tree rehydration - ', () => { + it('should correctly filter with a deserialized expression tree.', fakeAsync(() => { + const errorSpy = spyOn(console, 'error'); + let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); + fix.detectChanges(); + let grid = fix.componentInstance.grid; + + expect(errorSpy).not.toHaveBeenCalled(); + + // Verify filtered data + expect(grid.filteredData.length).toEqual(3); + expect(grid.rowList.length).toBe(3); + })); + + it('should correctly filter with a declared IFilteringExpressionsTree object.', fakeAsync(() => { + const errorSpy = spyOn(console, 'error'); + let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); + fix.detectChanges(); + fix.componentInstance.grid.advancedFilteringExpressionsTree = fix.componentInstance.filterTreeObject; + fix.detectChanges(); + let grid = fix.componentInstance.grid; + + expect(errorSpy).not.toHaveBeenCalled(); + + // Verify filtered data + expect(grid.filteredData.length).toEqual(2); + expect(grid.rowList.length).toBe(2); + })); + + it('should correctly filter when binding to a declared IFilteringExpressionsTree object.', fakeAsync(() => { + const errorSpy = spyOn(console, 'error'); + let fix = TestBed.createComponent(IgxGridAdvancedFilteringSerializedTreeComponent); + fix.detectChanges(); + fix.componentInstance.filterTree = fix.componentInstance.filterTreeObject; + fix.detectChanges(); + let grid = fix.componentInstance.grid; + + expect(errorSpy).not.toHaveBeenCalled(); + + // Verify filtered data + expect(grid.filteredData.length).toEqual(2); + expect(grid.rowList.length).toBe(2); + })); }); }); 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 2579254dc7f..e4ebe0c5ef7 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 @@ -2100,6 +2100,61 @@ export class IgxGridAdvancedFilteringBindingComponent extends BasicGridComponent } } +@Component({ + template: ` + + + + + + + `, + imports: [IgxGridComponent, IgxColumnComponent] +}) +export class IgxGridAdvancedFilteringSerializedTreeComponent extends BasicGridComponent implements OnInit { + public resizable = false; + public filterable = true; + public filterTree: IFilteringExpressionsTree; + public filterTreeObject: IFilteringExpressionsTree; + + public override data = SampleTestData.excelFilteringData(); + + public ngOnInit(): void { + this.filterTree = JSON.parse(`{ + "filteringOperands": [ + { + "conditionName": "greaterThan", + "fieldName": "Downloads", + "searchVal": 200 + } + ], + "operator": 0 + }`); + + this.filterTreeObject = { + "filteringOperands": [ + { + "fieldName": "ProductName", + "condition": { + "name": "contains", + "isUnary": false, + "iconName": "filter_contains" + }, + "conditionName": "contains", + "ignoreCase": true, + "searchVal": "Ig", + "searchTree": null + } + ], + "operator": 1, + "returnFields": [ + "ID", + "ProductName" + ] + }; + } +} + @Component({ template: `