Skip to content

Commit df43cfa

Browse files
committed
perf(utils): Improved internal utils performance
This is still work in progress and they will be additional commits. This aims to improve the baseline performance of some internal functions used across the grid data pipeline. These are relatively easy to improve before tackling the more complicated interactions.
1 parent 0d9ab22 commit df43cfa

File tree

13 files changed

+197
-198
lines changed

13 files changed

+197
-198
lines changed

projects/igniteui-angular/src/lib/core/utils.ts

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { mergeWith } from 'lodash-es';
44
import { NEVER, Observable } from 'rxjs';
55
import { setImmediate } from './setImmediate';
66
import { isDevMode } from '@angular/core';
7-
import { IgxTheme } from '../services/theme/theme.token';
7+
import type { IgxTheme } from '../services/theme/theme.token';
88

99
/** @hidden @internal */
1010
export const ELEMENTS_TOKEN = /*@__PURE__*/new InjectionToken<boolean>('elements environment');
@@ -31,17 +31,9 @@ export const getResizeObserver = () => globalThis.window?.ResizeObserver;
3131
/**
3232
* @hidden
3333
*/
34-
export const cloneArray = (array: any[], deep?: boolean) => {
35-
const arr = [];
36-
if (!array) {
37-
return arr;
38-
}
39-
let i = array.length;
40-
while (i--) {
41-
arr[i] = deep ? cloneValue(array[i]) : array[i];
42-
}
43-
return arr;
44-
};
34+
export function cloneArray<T>(array: T[], deep = false): T[] {
35+
return deep ? (array ?? []).map(cloneValue) : (array ?? []).slice();
36+
}
4537

4638
/**
4739
* Doesn't clone leaf items
@@ -108,7 +100,7 @@ export const cloneValue = (value: any): any => {
108100
return new Date(value.getTime());
109101
}
110102
if (Array.isArray(value)) {
111-
return [...value];
103+
return value.slice();
112104
}
113105

114106
if (value instanceof Map || value instanceof Set) {
@@ -298,7 +290,7 @@ export class PlatformUtil {
298290
*/
299291
public getNodeSizeViaRange(range: Range, node: HTMLElement, sizeHoldingNode?: HTMLElement) {
300292
let overflow = null;
301-
let nodeStyles;
293+
let nodeStyles: string[];
302294

303295
if (!this.isFirefox) {
304296
overflow = node.style.overflow;
@@ -455,17 +447,17 @@ export const resizeObservable = (target: HTMLElement): Observable<ResizeObserver
455447
// check whether we are on server env or client env
456448
if (resizeObserver) {
457449
return new Observable((observer) => {
458-
const instance = new resizeObserver((entries: ResizeObserverEntry[]) => {
459-
observer.next(entries);
460-
});
461-
instance.observe(target);
462-
const unsubscribe = () => instance.disconnect();
463-
return unsubscribe;
450+
const instance = new resizeObserver((entries: ResizeObserverEntry[]) => {
451+
observer.next(entries);
452+
});
453+
instance.observe(target);
454+
const unsubscribe = () => instance.disconnect();
455+
return unsubscribe;
464456
});
465-
} else {
466-
// if on a server env return a empty observable that does not complete immediately
467-
return NEVER;
468457
}
458+
// if on a server env return a empty observable that does not complete immediately
459+
return NEVER;
460+
469461
}
470462

471463
/**
@@ -476,7 +468,7 @@ export const resizeObservable = (target: HTMLElement): Observable<ResizeObserver
476468
*/
477469
export const compareMaps = (map1: Map<any, any>, map2: Map<any, any>): boolean => {
478470
if (!map2) {
479-
return !map1 ? true : false;
471+
return !map1;
480472
}
481473
if (map1.size !== map2.size) {
482474
return false;
@@ -496,26 +488,38 @@ export const compareMaps = (map1: Map<any, any>, map2: Map<any, any>): boolean =
496488
return match;
497489
};
498490

491+
function _isObject(entity: unknown): entity is object {
492+
return entity != null && typeof entity === 'object';
493+
}
494+
495+
export function columnFieldPath(path?: string): string[] {
496+
return path?.split('.') ?? [];
497+
}
498+
499499
/**
500-
*
501500
* Given a property access path in the format `x.y.z` resolves and returns
502501
* the value of the `z` property in the passed object.
503502
*
504503
* @hidden
505504
* @internal
506505
*/
507-
export const resolveNestedPath = (obj: any, path: string) => {
508-
const parts = path?.split('.') ?? [];
509-
let current = obj[parts.shift()];
506+
export function resolveNestedPath<T extends object, U>(obj: unknown, pathParts: string[], defaultValue?: U): T | U | undefined {
507+
if (!_isObject(obj)) {
508+
return defaultValue;
509+
}
510510

511-
parts.forEach(prop => {
512-
if (current) {
513-
current = current[prop];
511+
let current = obj;
512+
513+
for (const key of pathParts) {
514+
if (_isObject(current) && key in (current as T)) {
515+
current = current[key];
516+
} else {
517+
return defaultValue;
514518
}
515-
});
519+
}
516520

517-
return current;
518-
};
521+
return current as T;
522+
}
519523

520524
/**
521525
*
@@ -628,12 +632,12 @@ export function modulo(n: number, d: number) {
628632
* ```
629633
*/
630634
export function* intoChunks<T>(arr: T[], size: number) {
631-
if (size < 1) {
632-
throw new Error('size must be an integer >= 1');
633-
}
634-
for (let i = 0; i < arr.length; i += size) {
635-
yield arr.slice(i, i + size);
636-
}
635+
if (size < 1) {
636+
throw new Error('size must be an integer >= 1');
637+
}
638+
for (let i = 0; i < arr.length; i += size) {
639+
yield arr.slice(i, i + size);
640+
}
637641
}
638642

639643
/**
@@ -646,7 +650,6 @@ export function getComponentCssSizeVar(size: string) {
646650
return 'var(--ig-size, var(--ig-size-small))';
647651
case "2":
648652
return 'var(--ig-size, var(--ig-size-medium))';
649-
case "3":
650653
default:
651654
return 'var(--ig-size, var(--ig-size-large))';
652655
}
@@ -662,9 +665,9 @@ export function normalizeURI(path: string) {
662665

663666
export function getComponentTheme(el: Element) {
664667
return globalThis.window
665-
?.getComputedStyle(el)
666-
.getPropertyValue('--theme')
667-
.trim() as IgxTheme;
668+
?.getComputedStyle(el)
669+
.getPropertyValue('--theme')
670+
.trim() as IgxTheme;
668671
}
669672

670673
/**

projects/igniteui-angular/src/lib/data-operations/filtering-strategy.ts

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FilteringLogic, IFilteringExpression } from './filtering-expression.interface';
22
import { FilteringExpressionsTree, IFilteringExpressionsTree } from './filtering-expressions-tree';
3-
import { resolveNestedPath, parseDate, formatDate, formatCurrency } from '../core/utils';
3+
import { resolveNestedPath, parseDate, formatDate, formatCurrency, columnFieldPath } from '../core/utils';
44
import { ColumnType, EntityType, GridType } from '../grids/common/grid.interface';
55
import { GridColumnDataType } from './data-util';
66
import { SortingDirection } from './sorting-strategy';
@@ -26,7 +26,7 @@ export interface IFilteringStrategy {
2626
filter(data: any[], expressionsTree: IFilteringExpressionsTree, advancedExpressionsTree?: IFilteringExpressionsTree,
2727
grid?: GridType): any[];
2828
/* csSuppress */
29-
getFilterItems(column: ColumnType, tree: IFilteringExpressionsTree) : Promise<IgxFilterItem[]>;
29+
getFilterItems(column: ColumnType, tree: IFilteringExpressionsTree): Promise<IgxFilterItem[]>;
3030
}
3131

3232
/* csSuppress */
@@ -37,16 +37,14 @@ export interface IgxFilterItem {
3737
}
3838

3939
/* csSuppress */
40-
export abstract class BaseFilteringStrategy implements IFilteringStrategy {
40+
export abstract class BaseFilteringStrategy implements IFilteringStrategy {
4141
// protected
4242
public findMatchByExpression(rec: any, expr: IFilteringExpression, isDate?: boolean, isTime?: boolean, grid?: GridType): boolean {
4343
if (expr.searchTree) {
4444
const records = rec[expr.searchTree.entity];
4545
const shouldMatchRecords = expr.conditionName === 'inQuery';
4646
if (!records) { // child grid is not yet created
4747
return true;
48-
} else if (records.length === 0) { // child grid is empty
49-
return false;
5048
}
5149

5250
for (let index = 0; index < records.length; index++) {
@@ -74,7 +72,7 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
7472
const operator = expressionsTree.operator as FilteringLogic;
7573
let matchOperand;
7674

77-
if (expressionsTree.filteringOperands && expressionsTree.filteringOperands.length) {
75+
if (expressionsTree.filteringOperands?.length) {
7876
for (const operand of expressionsTree.filteringOperands) {
7977
matchOperand = this.matchRecord(rec, operand, grid, entity);
8078

@@ -137,9 +135,9 @@ export abstract class BaseFilteringStrategy implements IFilteringStrategy {
137135
data = column.grid.gridAPI.sortDataByExpressions(data,
138136
[{ fieldName: column.field, dir: SortingDirection.Asc, ignoreCase: column.sortingIgnoreCase }]);
139137

140-
const columnField = column.field;
138+
const pathParts = columnFieldPath(column.field)
141139
let filterItems: IgxFilterItem[] = data.map(record => {
142-
let value = resolveNestedPath(record, columnField);
140+
let value = resolveNestedPath(record, pathParts);
143141
const applyFormatter = column.formatter && this.shouldFormatFilterValues(column);
144142

145143
value = applyFormatter ?
@@ -239,36 +237,25 @@ export class NoopFilteringStrategy extends BaseFilteringStrategy {
239237
export class FilteringStrategy extends BaseFilteringStrategy {
240238
private static _instance: FilteringStrategy = null;
241239

242-
constructor() {
243-
super();
244-
}
245240

246241
public static instance() {
247242
return this._instance || (this._instance = new this());
248243
}
249244

250245
public filter<T>(data: T[], expressionsTree: IFilteringExpressionsTree, advancedExpressionsTree: IFilteringExpressionsTree,
251246
grid: GridType): T[] {
252-
let i;
253-
let rec;
254-
const len = data.length;
255-
const res: T[] = [];
256247

257-
if ((FilteringExpressionsTree.empty(expressionsTree) && FilteringExpressionsTree.empty(advancedExpressionsTree)) || !len) {
248+
249+
if ((FilteringExpressionsTree.empty(expressionsTree) && FilteringExpressionsTree.empty(advancedExpressionsTree))) {
258250
return data;
259251
}
260-
for (i = 0; i < len; i++) {
261-
rec = data[i];
262-
if (this.matchRecord(rec, expressionsTree, grid) && this.matchRecord(rec, advancedExpressionsTree, grid)) {
263-
res.push(rec);
264-
}
265-
}
266-
return res;
252+
253+
return data.filter(record => this.matchRecord(record, expressionsTree, grid) && this.matchRecord(record, advancedExpressionsTree, grid));
267254
}
268255

269256
protected getFieldValue(rec: any, fieldName: string, isDate = false, isTime = false, grid?: GridType): any {
270257
const column = grid?.getColumnByName(fieldName);
271-
let value = resolveNestedPath(rec, fieldName);
258+
let value = resolveNestedPath(rec, columnFieldPath(fieldName));
272259

273260
value = column?.formatter && this.shouldFormatFilterValues(column) ?
274261
column.formatter(value, rec) :

projects/igniteui-angular/src/lib/grids/common/pipes.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Pipe, PipeTransform, Inject } from '@angular/core';
22
import { DataUtil } from '../../data-operations/data-util';
3-
import { cloneArray, resolveNestedPath } from '../../core/utils';
3+
import { cloneArray, columnFieldPath, resolveNestedPath } from '../../core/utils';
44
import { GridType, IGX_GRID_BASE, RowType } from './grid.interface';
55
import { IgxAddRow } from './crud.service';
66
import { IgxSummaryOperand, IgxSummaryResult } from '../summaries/grid-summary';
@@ -26,11 +26,12 @@ export class IgxGridCellStyleClassesPipe implements PipeTransform {
2626
}
2727

2828
const result = [];
29+
const pathParts = columnFieldPath(field);
2930

3031
for (const cssClass of Object.keys(cssClasses)) {
3132
const callbackOrValue = cssClasses[cssClass];
3233
const apply = typeof callbackOrValue === 'function' ?
33-
callbackOrValue(data, field, resolveNestedPath(data, field), index) : callbackOrValue;
34+
callbackOrValue(data, field, resolveNestedPath(data, pathParts), index) : callbackOrValue;
3435
if (apply) {
3536
result.push(cssClass);
3637
}
@@ -57,9 +58,11 @@ export class IgxGridCellStylesPipe implements PipeTransform {
5758
return css;
5859
}
5960

61+
const pathParts = columnFieldPath(field);
62+
6063
for (const prop of Object.keys(styles)) {
6164
const res = styles[prop];
62-
css[prop] = typeof res === 'function' ? res(data, field, resolveNestedPath(data, field), index) : res;
65+
css[prop] = typeof res === 'function' ? res(data, field, resolveNestedPath(data, pathParts), index) : res;
6366
}
6467

6568
return css;
@@ -70,7 +73,7 @@ export class IgxGridCellStylesPipe implements PipeTransform {
7073
* @hidden
7174
* @internal
7275
*/
73-
@Pipe({
76+
@Pipe({
7477
name: 'igxCellImageAlt',
7578
standalone: true
7679
})
@@ -116,7 +119,7 @@ export class IgxGridRowClassesPipe implements PipeTransform {
116119
_rowData: any,
117120
_: number
118121
) {
119-
const result = new Set(['igx-grid__tr', index % 2 ? 'igx-grid__tr--even': 'igx-grid__tr--odd']);
122+
const result = new Set(['igx-grid__tr', index % 2 ? 'igx-grid__tr--even' : 'igx-grid__tr--odd']);
120123
const mapping = [
121124
[selected, 'igx-grid__tr--selected'],
122125
[editMode, 'igx-grid__tr--edit'],
@@ -329,7 +332,7 @@ export class IgxGridRowPinningPipe implements PipeTransform {
329332
export class IgxGridDataMapperPipe implements PipeTransform {
330333

331334
public transform(data: any[], field: string, _: number, val: any, isNestedPath: boolean) {
332-
return isNestedPath ? resolveNestedPath(data, field) : val;
335+
return isNestedPath ? resolveNestedPath(data, columnFieldPath(field)) : val;
333336
}
334337
}
335338

@@ -354,13 +357,13 @@ export class IgxGridTransactionStatePipe implements PipeTransform {
354357
if (rowEditable) {
355358
const rowCurrentState = transactions.getAggregatedValue(row_id, false);
356359
if (rowCurrentState) {
357-
const value = resolveNestedPath(rowCurrentState, field);
360+
const value = resolveNestedPath(rowCurrentState, columnFieldPath(field));
358361
return value !== undefined && value !== null;
359362
}
360363
} else {
361364
const transaction = transactions.getState(row_id);
362-
const value = resolveNestedPath(transaction?.value ?? {}, field);
363-
return transaction && transaction.value && (value || value === 0 || value === false);
365+
const value = resolveNestedPath(transaction?.value ?? {}, columnFieldPath(field));
366+
return transaction?.value && (value || value === 0 || value === false);
364367
}
365368
}
366369
}
@@ -371,7 +374,7 @@ export class IgxGridTransactionStatePipe implements PipeTransform {
371374
})
372375
export class IgxColumnFormatterPipe implements PipeTransform {
373376

374-
public transform(value: any, formatter: (v: any, data: any, columnData? :any) => any, rowData: any, columnData? : any) {
377+
public transform(value: any, formatter: (v: any, data: any, columnData?: any) => any, rowData: any, columnData?: any) {
375378
return formatter(value, rowData, columnData);
376379
}
377380
}

0 commit comments

Comments
 (0)