Skip to content
Closed
14 changes: 9 additions & 5 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

# Current Milestone

## Milestone 38, version 20.1 (Due by Sep, 2025)
1. Grid cell merging feature [#3514](https://github.com/IgniteUI/igniteui-angular/issues/3514)
2. “Clear Selection” button in Combo component is keyboard accessible [#15841](https://github.com/IgniteUI/igniteui-angular/issues/15841)
3. Selecting a slide by index in the Carousel component [#16046](https://github.com/IgniteUI/igniteui-angular/issues/16046)
## Milestone 40, version 20.1 (Due by Nov, 2025)
1. Support for Angular 21
2. AI Chat UI component [#16094](https://github.com/IgniteUI/igniteui-angular/issues/16094)

## Going down the road

1. AI Chat UI component [#16094](https://github.com/IgniteUI/igniteui-angular/issues/16094)
1. Grids performance

# Previous Milestone

## Milestone 39, version 20.1 (Released Sep 25th, 2025)
1. **[DONE]** Grid cell merging feature [#3514](https://github.com/IgniteUI/igniteui-angular/issues/3514)
2. **[DONE]** “Clear Selection” button in Combo component is keyboard accessible [#15841](https://github.com/IgniteUI/igniteui-angular/issues/15841)
3. **[DONE]** Selecting a slide by index in the Carousel component [#16046](https://github.com/IgniteUI/igniteui-angular/issues/16046)

## Milestone 38, version 20.0 (Released Jun 09th, 2025)
1. **[DONE]** Support of Angular 20.0

Expand Down
13 changes: 12 additions & 1 deletion projects/igniteui-angular/src/lib/data-operations/data-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IPagingState, PagingError } from './paging-state.interface';
import { IGroupByKey } from './groupby-expand-state.interface';
import { IGroupByRecord } from './groupby-record.interface';
import { IGroupingState } from './groupby-state.interface';
import { mergeObjects } from '../core/utils';
import { cloneArray, mergeObjects } from '../core/utils';
import { Transaction, TransactionType, HierarchicalTransaction } from '../services/transaction/transaction';
import { getHierarchy, isHierarchyMatch } from './operations';
import { ColumnType, GridType } from '../grids/common/grid.interface';
Expand All @@ -21,6 +21,8 @@ import {
import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations/data-clone-strategy';
import { IGroupingExpression } from './grouping-expression.interface';
import { DefaultMergeStrategy, IGridMergeStrategy } from './merge-strategy';
import { IFilteringExpressionsTree } from './filtering-expressions-tree';
import { FilteringStrategy, FilterUtil } from './filtering-strategy';

/**
* @hidden
Expand Down Expand Up @@ -278,6 +280,15 @@ export class DataUtil {
return value;
}

public static filterDataByExpressions(data: any[], expressionsTree: IFilteringExpressionsTree, grid: GridType): any {
if (expressionsTree.filteringOperands.length) {
const state = { expressionsTree, strategy: FilteringStrategy.instance() };
data = FilterUtil.filter(cloneArray(data), state, grid);
}

return data;
}

private static findParentFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any {
let collection: any[] = data;
let result: any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { FilteringLogic, type IFilteringExpression } from './filtering-expressio
import { FilteringExpressionsTree, type IFilteringExpressionsTree } from './filtering-expressions-tree';
import { resolveNestedPath, parseDate, formatDate, formatCurrency, columnFieldPath } from '../core/utils';
import type { ColumnType, EntityType, GridType } from '../grids/common/grid.interface';
import { GridColumnDataType } from './data-util';
import { DataUtil, GridColumnDataType } from './data-util';
import { SortingDirection } from './sorting-strategy';
import { formatNumber, formatPercent, getLocaleCurrencyCode } from '@angular/common';
import type { IFilteringState } from './filtering-state.interface';
import { isTree } from './expressions-tree-util';
import type { IgxHierarchicalGridComponent } from '../grids/hierarchical-grid/hierarchical-grid.component';
import { IgxSorting } from '../grids/common/strategy';

const DateType = 'date';
const DateTimeType = 'dateTime';
Expand Down Expand Up @@ -132,27 +133,35 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
public getFilterItems(column: ColumnType, tree: IFilteringExpressionsTree): Promise<IgxFilterItem[]> {
const applyFormatter = column.formatter && this.shouldFormatFilterValues(column);

let data = column.grid.gridAPI.filterDataByExpressions(tree);
data = column.grid.gridAPI.sortDataByExpressions(data,
[{ fieldName: column.field, dir: SortingDirection.Asc, ignoreCase: column.sortingIgnoreCase }]);

const data = this.getFilteredData(column, tree);

const pathParts = columnFieldPath(column.field)
let filterItems: IgxFilterItem[] = data.map(record => {
const value = applyFormatter ?
column.formatter(resolveNestedPath(record, pathParts), record) :
resolveNestedPath(record, pathParts);

return {
value,
label: this.getFilterItemLabel(column, value, !applyFormatter, record)
};
});
filterItems = this.getUniqueFilterItems(column, filterItems);
const seenFormattedFilterItems = new Map<any, IgxFilterItem>()

for (let i = 0; i < data.length; ++i) {
const record = data[i]
const rawValue = resolveNestedPath(record, pathParts);
const formattedValue = applyFormatter ? column.formatter(rawValue, record) : rawValue;
const { key, finalValue } = this.getFilterItemKeyValue(formattedValue, column);
// Deduplicate by normalized key
if (!seenFormattedFilterItems.has(key)) {
const label = this.getFilterItemLabel(column, finalValue, !applyFormatter, record);
seenFormattedFilterItems.set(key, { value: finalValue, label });
}
}

let filterItems: IgxFilterItem[] = Array.from(seenFormattedFilterItems.values());

filterItems = DataUtil.sort(filterItems,
[{ fieldName: 'value', dir: SortingDirection.Asc, ignoreCase: column.sortingIgnoreCase }], new IgxSorting())

return Promise.resolve(filterItems);
}

protected getFilteredData(column: ColumnType, tree: IFilteringExpressionsTree) {
return column.grid.gridAPI.filterDataByExpressions(tree);
}

protected getFilterItemLabel(column: ColumnType, value: any, applyFormatter: boolean, data: any) {
if (column.formatter) {
if (applyFormatter) {
Expand Down Expand Up @@ -180,30 +189,33 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
}
}

protected getUniqueFilterItems(column: ColumnType, filterItems: IgxFilterItem[]) {
const filteredUniqueValues = filterItems.reduce((map, item) => {
let key = item.value;

if (column.dataType === GridColumnDataType.String && column.filteringIgnoreCase) {
key = key?.toString().toLowerCase();
} else if (column.dataType === GridColumnDataType.DateTime) {
key = item.value?.toString();
item.value = key ? new Date(key) : key;
} else if (column.dataType === GridColumnDataType.Time) {
const date = key ? new Date(key) : key;
key = date ? new Date().setHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()) : key;
item.value = key ? new Date(key) : key;
} else if (column.dataType === GridColumnDataType.Date) {
const date = key ? new Date(key) : key;
key = date ? new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString() : key;
item.value = date;
}

return map.has(key) ? map : map.set(key, item)
}, new Map());
const uniqueValues = Array.from(filteredUniqueValues.values());

return uniqueValues;
protected getFilterItemKeyValue(value: any, column: ColumnType) {
let key: any = value;
let finalValue = value;
if (column.dataType === GridColumnDataType.String && column.filteringIgnoreCase) {
key = key?.toString().toLowerCase();
} else if (column.dataType === GridColumnDataType.DateTime) {
key = value?.toString();
finalValue = key ? new Date(key) : key;
} else if (column.dataType === GridColumnDataType.Time) {
const date = key ? new Date(key) : key;
key = date
? new Date().setHours(
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds()
)
: date;
finalValue = key ? new Date(key) : key;
} else if (column.dataType === GridColumnDataType.Date) {
const date = key ? new Date(key) : key;
key = date
? new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString()
: date;
finalValue = date;
}
return { key, finalValue };
}

protected shouldFormatFilterValues(_column: ColumnType): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,46 @@ describe('Unit testing SortingStrategy', () => {
strategy: DefaultSortingStrategy.instance()
}]);
expect(dataGenerator.getValuesForColumn(res, 'number'))
.toEqual([4, 2, 0, 3, 1]);
.toEqual([4, 2, 0, 3, 1]);
});
it('tests `compareObjects`', () => {
const strategy = DefaultSortingStrategy.instance();
expect(strategy.compareValues(1, 0) === 1 &&
strategy.compareValues(true, false) === 1 &&
strategy.compareValues('bc', 'adfc') === 1)
strategy.compareValues(true, false) === 1 &&
strategy.compareValues('bc', 'adfc') === 1)
.toBeTruthy('compare first argument greater than second');
expect(strategy.compareValues(1, 2) === -1 &&
strategy.compareValues('a', 'b') === -1 &&
strategy.compareValues(false, true) === -1)
strategy.compareValues('a', 'b') === -1 &&
strategy.compareValues(false, true) === -1)
.toBeTruthy('compare 0, 1');
expect(strategy.compareValues(0, 0) === 0 &&
strategy.compareValues(true, true) === 0 &&
strategy.compareValues('test', 'test') === 0
)
strategy.compareValues(true, true) === 0 &&
strategy.compareValues('test', 'test') === 0
)
.toBeTruthy('Comare equal variables');
});
it('tests default settings', () => {
(data[4] as { string: string }).string = 'ROW';
const res = sorting.sort(data, [{
dir: SortingDirection.Asc,
fieldName: 'string',
ignoreCase: true,
strategy: DefaultSortingStrategy.instance()
}]);
dir: SortingDirection.Asc,
fieldName: 'string',
ignoreCase: true,
strategy: DefaultSortingStrategy.instance()
}]);
expect(dataGenerator.getValuesForColumn(res, 'number'))
.toEqual([4, 0, 1, 2, 3]);
.toEqual([4, 0, 1, 2, 3]);
});

it('should not sort when sorting direction is None', () => {
const unsortedData = [{ number: 3 }, { number: 1 }, { number: 4 }, { number: 0 }, { number: 2 }];
const res = sorting.sort(unsortedData, [{
dir: SortingDirection.None,
fieldName: 'number',
ignoreCase: false,
strategy: DefaultSortingStrategy.instance()
}]);
expect(res.map(d => d.number))
.toEqual([3, 1, 4, 0, 2]);
});

});
5 changes: 4 additions & 1 deletion projects/igniteui-angular/src/lib/grids/common/strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IGroupingState } from '../../data-operations/groupby-state.interface';
import { IGroupingExpression } from '../../data-operations/grouping-expression.interface';
import { IGroupByResult } from '../../data-operations/grouping-result.interface';
import { getHierarchy, isHierarchyMatch } from '../../data-operations/operations';
import { DefaultSortingStrategy, ISortingExpression } from '../../data-operations/sorting-strategy';
import { DefaultSortingStrategy, ISortingExpression, SortingDirection } from '../../data-operations/sorting-strategy';
import { GridType } from './grid.interface';

const DATE_TYPE = 'date';
Expand Down Expand Up @@ -141,6 +141,9 @@ export class IgxSorting implements IGridSortingStrategy {
private prepareExpressions(expressions: ISortingExpression[], grid: GridType): IGridInternalSortingExpression[] {
const multipleSortingExpressions: IGridInternalSortingExpression[] = [];
for (const expr of expressions) {
if (expr.dir === SortingDirection.None) {
continue;
}
if (!expr.strategy) {
expr.strategy = DefaultSortingStrategy.instance();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export abstract class BaseFilteringComponent {
public abstract columnChange: EventEmitter<any>;
public abstract sortingChanged: EventEmitter<undefined>;
public abstract listDataLoaded: EventEmitter<undefined>;
public abstract filterCleared: EventEmitter<undefined>;

constructor(
protected cdr: ChangeDetectorRef,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class IgxExcelStyleClearFiltersComponent {
*/
public clearFilter() {
this.esf.grid.filteringService.clearFilter(this.esf.column.field);
this.esf.filterCleared.emit();
this.selectAllFilterItems();
}

Expand Down
Loading
Loading