• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 18376174747

09 Oct 2025 12:22PM UTC coverage: 91.539% (-0.05%) from 91.585%
18376174747

push

github

web-flow
perf(filtering): Optimize filtering performance (#16208)

---------

Co-authored-by: Radoslav Karaivanov <rkaraivanov@infragistics.com>

13826 of 16225 branches covered (85.21%)

81 of 88 new or added lines in 9 files covered. (92.05%)

16 existing lines in 3 files now uncovered.

27803 of 30373 relevant lines covered (91.54%)

34540.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.19
/projects/igniteui-angular/src/lib/data-operations/data-util.ts
1
import { IGroupByResult } from './grouping-result.interface';
2

3
import { IPagingState, PagingError } from './paging-state.interface';
4

5
import { IGroupByKey } from './groupby-expand-state.interface';
6
import { IGroupByRecord } from './groupby-record.interface';
7
import { IGroupingState } from './groupby-state.interface';
8
import { cloneArray, mergeObjects } from '../core/utils';
9
import { Transaction, TransactionType, HierarchicalTransaction } from '../services/transaction/transaction';
10
import { getHierarchy, isHierarchyMatch } from './operations';
11
import { ColumnType, GridType } from '../grids/common/grid.interface';
12
import { ITreeGridRecord } from '../grids/tree-grid/tree-grid.interfaces';
13
import { ISortingExpression } from './sorting-strategy';
14
import {
15
    IGridSortingStrategy,
16
    IGridGroupingStrategy,
17
    IgxDataRecordSorting,
18
    IgxSorting,
19
    IgxGrouping
20
} from '../grids/common/strategy';
21
import { DefaultDataCloneStrategy, IDataCloneStrategy } from '../data-operations/data-clone-strategy';
22
import { IGroupingExpression } from './grouping-expression.interface';
23
import { DefaultMergeStrategy, IGridMergeStrategy } from './merge-strategy';
24
import { IFilteringExpressionsTree } from './filtering-expressions-tree';
25
import { FilteringStrategy, FilterUtil } from './filtering-strategy';
26

27
/**
28
 * @hidden
29
 */
30
export const DataType = {
3✔
31
    String: 'string',
32
    Number: 'number',
33
    Boolean: 'boolean',
34
    Date: 'date',
35
    DateTime: 'dateTime',
36
    Time: 'time',
37
    Currency: 'currency',
38
    Percent: 'percent',
39
    Image: 'image'
40
} as const;
41
export type DataType = (typeof DataType)[keyof typeof DataType];
42

43
/**
44
 * @hidden
45
 */
46
export const GridColumnDataType = DataType;
3✔
47
export type GridColumnDataType = (typeof GridColumnDataType)[keyof typeof GridColumnDataType];
48

49
/**
50
 * @hidden
51
 */
52
export class DataUtil {
53
    public static sort<T>(data: T[], expressions: ISortingExpression[], sorting: IGridSortingStrategy = new IgxSorting(),
832✔
54
        grid?: GridType): T[] {
55
        return sorting.sort(data, expressions, grid);
1,576✔
56
    }
57

58
    public static treeGridSort(hierarchicalData: ITreeGridRecord[],
59
        expressions: ISortingExpression[],
60
        sorting: IGridSortingStrategy = new IgxDataRecordSorting(),
78✔
61
        grid?: GridType): ITreeGridRecord[] {
62
        const res: ITreeGridRecord[] = [];
78✔
63
        const stack: {
64
            original: ITreeGridRecord[];
65
            parent?: ITreeGridRecord;
66
            result: ITreeGridRecord[];
67
        }[] = [];
78✔
68

69
        stack.push({ original: hierarchicalData, parent: null, result: res });
78✔
70

71
        while (stack.length > 0) {
78✔
72
            const { original, parent, result } = stack.pop()!;
540✔
73

74
            const clonedRecords: ITreeGridRecord[] = [];
540✔
75

76
            for (const treeRecord of original) {
540✔
77
                const rec: ITreeGridRecord = DataUtil.cloneTreeGridRecord(treeRecord);
1,164✔
78
                rec.parent = parent;
1,164✔
79
                clonedRecords.push(rec);
1,164✔
80

81
                // If it has children, process them later
82
                if (rec.children && rec.children.length > 0) {
1,164✔
83
                    const childClones: ITreeGridRecord[] = [];
462✔
84
                    rec.children = childClones;
462✔
85
                    stack.push({
462✔
86
                        original: treeRecord.children,
87
                        parent: rec,
88
                        result: childClones
89
                    });
90
                }
91
            }
92

93
            // Sort the clonedRecords before assigning to the result
94
            const sorted = DataUtil.sort(clonedRecords, expressions, sorting, grid);
540✔
95
            for (const item of sorted) {
540✔
96
                result.push(item);
1,164✔
97
            }
98
        }
99

100
        return res;
78✔
101
    }
102

103
    public static cloneTreeGridRecord(hierarchicalRecord: ITreeGridRecord) {
104
        const rec: ITreeGridRecord = {
2,179✔
105
            key: hierarchicalRecord.key,
106
            data: hierarchicalRecord.data,
107
            children: hierarchicalRecord.children,
108
            isFilteredOutParent: hierarchicalRecord.isFilteredOutParent,
109
            level: hierarchicalRecord.level,
110
            expanded: hierarchicalRecord.expanded
111
        };
112
        return rec;
2,179✔
113
    }
114

115
    public static group<T>(data: T[], state: IGroupingState, grouping: IGridGroupingStrategy = new IgxGrouping(), grid: GridType = null,
421✔
116
        groupsRecords: any[] = [], fullResult: IGroupByResult = { data: [], metadata: [] }): IGroupByResult {
16✔
117
        groupsRecords.splice(0, groupsRecords.length);
450✔
118
        return grouping.groupBy(data, state, grid, groupsRecords, fullResult);
450✔
119
    }
120

121
    public static merge<T>(data: T[], columns: ColumnType[], strategy: IGridMergeStrategy = new DefaultMergeStrategy(), activeRowIndexes = [], grid: GridType = null,
×
122
    ): any[] {
123
        const result = [];
87✔
124
        for (const col of columns) {
87✔
125
            const isDate = col?.dataType === 'date' || col?.dataType === 'dateTime';
89✔
126
            const isTime = col?.dataType === 'time' || col?.dataType === 'dateTime';
89✔
127
            strategy.merge(
89✔
128
                data,
129
                col.field,
130
                col.mergingComparer,
131
                result,
132
                activeRowIndexes,
133
                isDate,
134
                isTime,
135
                grid);
136
        }
137
        return result;
87✔
138
    }
139

140
    public static page<T>(data: T[], state: IPagingState, dataLength?: number): T[] {
141
        if (!state) {
903✔
142
            return data;
1✔
143
        }
144
        const len = dataLength !== undefined ? dataLength : data.length;
902✔
145
        const index = state.index;
902✔
146
        const res = [];
902✔
147
        const recordsPerPage = dataLength !== undefined && state.recordsPerPage > dataLength ? dataLength : state.recordsPerPage;
902✔
148
        state.metadata = {
902✔
149
            countPages: 0,
150
            countRecords: len,
151
            error: PagingError.None
152
        };
153
        if (index < 0 || isNaN(index)) {
902✔
154
            state.metadata.error = PagingError.IncorrectPageIndex;
15✔
155
            return res;
15✔
156
        }
157
        if (recordsPerPage <= 0 || isNaN(recordsPerPage)) {
887✔
158
            state.metadata.error = PagingError.IncorrectRecordsPerPage;
6✔
159
            return res;
6✔
160
        }
161
        state.metadata.countPages = Math.ceil(len / recordsPerPage);
881✔
162
        if (!len) {
881!
163
            return data;
×
164
        }
165
        if (index >= state.metadata.countPages) {
881✔
166
            state.metadata.error = PagingError.IncorrectPageIndex;
1✔
167
            return res;
1✔
168
        }
169
        return data.slice(index * recordsPerPage, (index + 1) * recordsPerPage);
880✔
170
    }
171

172
    public static correctPagingState(state: IPagingState, length: number) {
173
        const maxPage = Math.ceil(length / state.recordsPerPage) - 1;
308✔
174
        if (!isNaN(maxPage) && state.index > maxPage) {
308✔
175
            state.index = maxPage;
10✔
176
        }
177
    }
178

179
    public static getHierarchy(gRow: IGroupByRecord): Array<IGroupByKey> {
180
        return getHierarchy(gRow);
15,007✔
181
    }
182

183
    public static isHierarchyMatch(h1: Array<IGroupByKey>, h2: Array<IGroupByKey>, expressions: IGroupingExpression[]): boolean {
184
        return isHierarchyMatch(h1, h2, expressions);
1,493✔
185
    }
186

187
    /**
188
     * Merges all changes from provided transactions into provided data collection
189
     *
190
     * @param data Collection to merge
191
     * @param transactions Transactions to merge into data
192
     * @param primaryKey Primary key of the collection, if any
193
     * @param deleteRows Should delete rows with DELETE transaction type from data
194
     * @returns Provided data collections updated with all provided transactions
195
     */
196
    public static mergeTransactions<T>(data: T[], transactions: Transaction[], primaryKey?: any, cloneStrategy: IDataCloneStrategy = new DefaultDataCloneStrategy(), deleteRows = false): T[] {
620✔
197
        data.forEach((item: any, index: number) => {
619✔
198
            const rowId = primaryKey ? item[primaryKey] : item;
6,684✔
199
            const transaction = transactions.find(t => t.id === rowId);
6,684✔
200
            if (transaction && transaction.type === TransactionType.UPDATE) {
6,684✔
201
                data[index] = mergeObjects(cloneStrategy.clone(data[index]), transaction.newValue);
116✔
202
            }
203
        });
204

205
        if (deleteRows) {
619✔
206
            transactions
1✔
207
                .filter(t => t.type === TransactionType.DELETE)
2✔
208
                .forEach(t => {
209
                    const index = primaryKey ? data.findIndex(d => d[primaryKey] === t.id) : data.findIndex(d => d === t.id);
3!
210
                    if (0 <= index && index < data.length) {
2✔
211
                        data.splice(index, 1);
2✔
212
                    }
213
                });
214
        }
215

216
        data.push(...transactions
619✔
217
            .filter(t => t.type === TransactionType.ADD)
305✔
218
            .map(t => t.newValue));
113✔
219

220
        return data;
619✔
221
    }
222

223
    /**
224
     * Merges all changes from provided transactions into provided hierarchical data collection
225
     *
226
     * @param data Collection to merge
227
     * @param transactions Transactions to merge into data
228
     * @param childDataKey Data key of child collections
229
     * @param primaryKey Primary key of the collection, if any
230
     * @param deleteRows Should delete rows with DELETE transaction type from data
231
     * @returns Provided data collections updated with all provided transactions
232
     */
233
    public static mergeHierarchicalTransactions(
234
        data: any[],
235
        transactions: HierarchicalTransaction[],
236
        childDataKey: any,
237
        primaryKey?: any,
238
        cloneStrategy: IDataCloneStrategy = new DefaultDataCloneStrategy(),
×
239
        deleteRows = false): any[] {
63✔
240
        for (const transaction of transactions) {
75✔
241
            if (transaction.path) {
98✔
242
                const parent = this.findParentFromPath(data, primaryKey, childDataKey, transaction.path);
86✔
243
                let collection: any[] = parent ? parent[childDataKey] : data;
86✔
244
                switch (transaction.type) {
86✔
245
                    case TransactionType.ADD:
246
                        //  if there is no parent this is ADD row at root level
247
                        if (parent && !parent[childDataKey]) {
27✔
248
                            parent[childDataKey] = collection = [];
10✔
249
                        }
250
                        collection.push(transaction.newValue);
27✔
251
                        break;
27✔
252
                    case TransactionType.UPDATE:
253
                        const updateIndex = collection.findIndex(x => x[primaryKey] === transaction.id);
48✔
254
                        if (updateIndex !== -1) {
35✔
255
                            collection[updateIndex] = mergeObjects(cloneStrategy.clone(collection[updateIndex]), transaction.newValue);
35✔
256
                        }
257
                        break;
35✔
258
                    case TransactionType.DELETE:
259
                        if (deleteRows) {
24✔
260
                            const deleteIndex = collection.findIndex(r => r[primaryKey] === transaction.id);
10✔
261
                            if (deleteIndex !== -1) {
6✔
262
                                collection.splice(deleteIndex, 1);
6✔
263
                            }
264
                        }
265
                        break;
24✔
266
                }
267
            } else {
268
                //  if there is no path this is ADD row in root. Push the newValue to data
269
                data.push(transaction.newValue);
12✔
270
            }
271
        }
272
        return data;
75✔
273
    }
274

275
    public static parseValue(dataType: GridColumnDataType, value: any): any {
276
        if (dataType === GridColumnDataType.Number || dataType === GridColumnDataType.Currency || dataType === GridColumnDataType.Percent) {
102✔
277
            value = parseFloat(value);
42✔
278
        }
279

280
        return value;
102✔
281
    }
282

283
    public static filterDataByExpressions(data: any[], expressionsTree: IFilteringExpressionsTree, grid: GridType): any {
284
        if (expressionsTree.filteringOperands.length) {
2!
NEW
285
            const state = { expressionsTree, strategy: FilteringStrategy.instance() };
×
NEW
286
            data = FilterUtil.filter(cloneArray(data), state, grid);
×
287
        }
288

289
        return data;
2✔
290
    }
291

292
    private static findParentFromPath(data: any[], primaryKey: any, childDataKey: any, path: any[]): any {
293
        let collection: any[] = data;
86✔
294
        let result: any;
295

296
        for (const id of path) {
86✔
297
            result = collection && collection.find(x => x[primaryKey] === id);
142✔
298
            if (!result) {
89!
299
                break;
×
300
            }
301

302
            collection = result[childDataKey];
89✔
303
        }
304

305
        return result;
86✔
306
    }
307
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc