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

IgniteUI / igniteui-angular / 26023601418

18 May 2026 08:57AM UTC coverage: 4.854% (-85.3%) from 90.174%
26023601418

Pull #17281

github

web-flow
Merge e7ce7a18e into 5a85df190
Pull Request #17281: feat: Added virtual scroll component and sample implementation

400 of 17347 branches covered (2.31%)

Branch coverage included in aggregate %.

63 of 222 new or added lines in 4 files covered. (28.38%)

27932 existing lines in 341 files now uncovered.

2022 of 32547 relevant lines covered (6.21%)

0.72 hits per line

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

7.77
/projects/igniteui-angular/core/src/core/utils.ts
1
import { isPlatformBrowser } from '@angular/common';
2
import { Injectable, InjectionToken, PLATFORM_ID, inject } from '@angular/core';
3
import { mergeWith } from 'lodash-es';
4
import { NEVER, Observable } from 'rxjs';
5
import { isDevMode } from '@angular/core';
6
import type { IgxTheme } from '../services/theme/theme.token';
7

8
/** @hidden @internal */
9
export const ELEMENTS_TOKEN = /*@__PURE__*/new InjectionToken<boolean>('elements environment');
3✔
10

11

12
/**
13
 * Returns true if the element's direction is left-to-right
14
 *
15
 * @hidden @internal
16
 */
17
export function isLeftToRight(element: Element): boolean {
UNCOV
18
    return element?.matches(':dir(ltr)') ?? true;
×
19
}
20

21
/**
22
 * @hidden
23
 */
24
export const showMessage = (message: string, isMessageShown: boolean): boolean => {
3✔
25
    if (!isMessageShown && isDevMode()) {
×
26
        console.warn(message);
×
27
    }
28

29
    return true;
×
30
};
31

32
/**
33
 *
34
 * @hidden @internal
35
 */
36
export const getResizeObserver = () => globalThis.window?.ResizeObserver;
3✔
37

38
/**
39
 * @hidden
40
 */
41
export function cloneArray<T>(array: T[], deep = false): T[] {
×
UNCOV
42
    return deep ? (array ?? []).map(cloneValue) : (array ?? []).slice();
×
43
}
44

45
/**
46
 * @hidden
47
 */
48
export function areEqualArrays<T>(arr1: T[], arr2: T[]): boolean {
UNCOV
49
    if (arr1.length !== arr2.length) return false;
×
UNCOV
50
        for (let i = 0; i < arr1.length; i++) {
×
UNCOV
51
          if (arr1[i] !== arr2[i]) return false;
×
52
        }
UNCOV
53
        return true;
×
54
}
55

56
/**
57
 * Doesn't clone leaf items
58
 *
59
 * @hidden
60
 */
61
export const cloneHierarchicalArray = (array: any[], childDataKey: any): any[] => {
3✔
UNCOV
62
    const result: any[] = [];
×
UNCOV
63
    if (!array) {
×
64
        return result;
×
65
    }
66

UNCOV
67
    for (const item of array) {
×
UNCOV
68
        const clonedItem = cloneValue(item);
×
UNCOV
69
        if (Array.isArray(item[childDataKey])) {
×
UNCOV
70
            clonedItem[childDataKey] = cloneHierarchicalArray(clonedItem[childDataKey], childDataKey);
×
71
        }
UNCOV
72
        result.push(clonedItem);
×
73
    }
UNCOV
74
    return result;
×
75
};
76

77
/**
78
 * Creates an object with prototype from provided source and copies
79
 * all properties descriptors from provided source
80
 * @param obj Source to copy prototype and descriptors from
81
 * @returns New object with cloned prototype and property descriptors
82
 */
83
export const copyDescriptors = (obj) => {
3✔
UNCOV
84
    if (obj) {
×
UNCOV
85
        return Object.create(
×
86
            Object.getPrototypeOf(obj),
87
            Object.getOwnPropertyDescriptors(obj)
88
        );
89
    }
90
}
91

92

93
/**
94
 * Deep clones all first level keys of Obj2 and merges them to Obj1
95
 *
96
 * @param obj1 Object to merge into
97
 * @param obj2 Object to merge from
98
 * @returns Obj1 with merged cloned keys from Obj2
99
 * @hidden
100
 */
101
export const mergeObjects = (obj1: any, obj2: any): any => mergeWith(obj1, obj2, (objValue, srcValue) => {
3✔
UNCOV
102
    if (Array.isArray(srcValue)) {
×
UNCOV
103
        return objValue = srcValue;
×
104
    }
105
});
106

107
/**
108
 * Creates deep clone of provided value.
109
 * Supports primitive values, dates and objects.
110
 * If passed value is array returns shallow copy of the array.
111
 *
112
 * @param value value to clone
113
 * @returns Deep copy of provided value
114
 * @hidden
115
 */
116
export const cloneValue = (value: any): any => {
3✔
UNCOV
117
    if (isDate(value)) {
×
UNCOV
118
        return new Date(value.getTime());
×
119
    }
UNCOV
120
    if (Array.isArray(value)) {
×
UNCOV
121
        return value.slice();
×
122
    }
123

UNCOV
124
    if (value instanceof Map || value instanceof Set) {
×
UNCOV
125
        return value;
×
126
    }
127

UNCOV
128
    if (isObject(value)) {
×
UNCOV
129
        const result = {};
×
130

UNCOV
131
        for (const key of Object.keys(value)) {
×
UNCOV
132
            if (key === "externalObject") {
×
133
                continue;
×
134
            }
UNCOV
135
            result[key] = cloneValue(value[key]);
×
136
        }
UNCOV
137
        return result;
×
138
    }
UNCOV
139
    return value;
×
140
};
141

142
/**
143
 * Creates deep clone of provided value.
144
 * Supports primitive values, dates and objects.
145
 * If passed value is array returns shallow copy of the array.
146
 * For Objects property values and references are cached and reused.
147
 * This allows for circular references to same objects.
148
 *
149
 * @param value value to clone
150
 * @param cache map of cached values already parsed
151
 * @returns Deep copy of provided value
152
 * @hidden
153
 */
154
export const cloneValueCached = (value: any, cache: Map<any, any>): any => {
3✔
155
    if (isDate(value)) {
×
156
        return new Date(value.getTime());
×
157
    }
158
    if (Array.isArray(value)) {
×
159
        return [...value];
×
160
    }
161

162
    if (value instanceof Map || value instanceof Set) {
×
163
        return value;
×
164
    }
165

166
    if (isObject(value)) {
×
167
        if (cache.has(value)) {
×
168
            return cache.get(value);
×
169
        }
170

171
        const result = {};
×
172
        cache.set(value, result);
×
173

174
        for (const key of Object.keys(value)) {
×
175
            result[key] = cloneValueCached(value[key], cache);
×
176
        }
177
        return result;
×
178
    }
179
    return value;
×
180
};
181

182
/**
183
 * Parse provided input to Date.
184
 *
185
 * @param value input to parse
186
 * @returns Date if parse succeed or null
187
 * @hidden
188
 */
189
export const parseDate = (value: any): Date | null => {
3✔
190
    // if value is Invalid Date return null
UNCOV
191
    if (isDate(value)) {
×
UNCOV
192
        return !isNaN(value.getTime()) ? value : null;
×
193
    }
UNCOV
194
    return value ? new Date(value) : null;
×
195
};
196

197
/**
198
 * Returns an array with unique dates only.
199
 *
200
 * @param columnValues collection of date values (might be numbers or ISO 8601 strings)
201
 * @returns collection of unique dates.
202
 * @hidden
203
 */
204
export const uniqueDates = (columnValues: any[]) => columnValues.reduce((a, c) => {
3✔
205
    if (!a.cache[c.label]) {
×
206
        a.result.push(c);
×
207
    }
208
    a.cache[c.label] = true;
×
209
    return a;
×
210
}, { result: [], cache: {} }).result;
211

212
/**
213
 * Checks if provided variable is Object
214
 *
215
 * @param value Value to check
216
 * @returns true if provided variable is Object
217
 * @hidden
218
 */
219
export const isObject = (value: any): boolean => !!(value && value.toString() === '[object Object]');
3!
220

221
/**
222
 * Checks if provided variable is Date
223
 *
224
 * @param value Value to check
225
 * @returns true if provided variable is Date
226
 * @hidden
227
 */
228
export const isDate = (value: any): value is Date => {
3✔
UNCOV
229
    return Object.prototype.toString.call(value) === "[object Date]";
×
230
}
231

232
/**
233
 * Checks if the two passed arguments are equal
234
 * Currently supports date objects
235
 *
236
 * @param obj1
237
 * @param obj2
238
 * @returns: `boolean`
239
 * @hidden
240
 */
241
export const isEqual = (obj1, obj2): boolean => {
3✔
UNCOV
242
    if (isDate(obj1) && isDate(obj2)) {
×
UNCOV
243
        return obj1.getTime() === obj2.getTime();
×
244
    }
UNCOV
245
    return obj1 === obj2;
×
246
};
247

248
/**
249
 * Limits a number to a range between a minimum and a maximum value.
250
 *
251
 * @param number
252
 * @param min
253
 * @param max
254
 * @returns: `number`
255
 * @hidden
256
 */
257
export const clamp = (number: number, min: number, max: number) =>
3✔
UNCOV
258
    Math.max(min, Math.min(number, max));
×
259

260

261
/**
262
 * Utility service taking care of various utility functions such as
263
 * detecting browser features, general cross browser DOM manipulation, etc.
264
 *
265
 * @hidden @internal
266
 */
267
@Injectable({ providedIn: 'root' })
268
export class PlatformUtil {
3✔
UNCOV
269
    private platformId = inject(PLATFORM_ID);
×
270

UNCOV
271
    public isBrowser: boolean = isPlatformBrowser(this.platformId);
×
UNCOV
272
    public isIOS = this.isBrowser && /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window);
×
UNCOV
273
    public isSafari = this.isBrowser && /Safari[\/\s](\d+\.\d+)/.test(navigator.userAgent);
×
UNCOV
274
    public isFirefox = this.isBrowser && /Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent);
×
UNCOV
275
    public isEdge = this.isBrowser && /Edge[\/\s](\d+\.\d+)/.test(navigator.userAgent);
×
UNCOV
276
    public isChromium = this.isBrowser && (/Chrom|e?ium/g.test(navigator.userAgent) ||
×
277
        /Google Inc/g.test(navigator.vendor)) && !/Edge/g.test(navigator.userAgent);
UNCOV
278
    public browserVersion = this.isBrowser ? parseFloat(navigator.userAgent.match(/Version\/([\d.]+)/)?.at(1)) : 0;
×
279

280
    /** @hidden @internal */
UNCOV
281
    public isElements = inject(ELEMENTS_TOKEN, { optional: true });
×
282

UNCOV
283
    public KEYMAP = {
×
284
        ENTER: 'Enter',
285
        SPACE: ' ',
286
        ESCAPE: 'Escape',
287
        ARROW_DOWN: 'ArrowDown',
288
        ARROW_UP: 'ArrowUp',
289
        ARROW_LEFT: 'ArrowLeft',
290
        ARROW_RIGHT: 'ArrowRight',
291
        END: 'End',
292
        HOME: 'Home',
293
        PAGE_DOWN: 'PageDown',
294
        PAGE_UP: 'PageUp',
295
        F2: 'F2',
296
        TAB: 'Tab',
297
        SEMICOLON: ';',
298
        // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values#editing_keys
299
        DELETE: 'Delete',
300
        BACKSPACE: 'Backspace',
301
        CONTROL: 'Control',
302
        X: 'x',
303
        Y: 'y',
304
        Z: 'z'
305
    } as const;
306

307
    /**
308
     * @hidden @internal
309
     * Returns the actual size of the node content, using Range
310
     * ```typescript
311
     * let range = document.createRange();
312
     * let column = this.grid.columnList.filter(c => c.field === 'ID')[0];
313
     *
314
     * let size = getNodeSizeViaRange(range, column.cells[0].nativeElement);
315
     *
316
     * @remarks
317
     * The last parameter is useful when the size of the element to measure is modified by a
318
     * parent element that has explicit size. In such cases the calculated size is never lower
319
     * and the function may instead remove the parent size while measuring to get the correct value.
320
     * ```
321
     */
322
    public getNodeSizeViaRange(range: Range, node: HTMLElement, sizeHoldingNode?: HTMLElement) {
UNCOV
323
        let overflow = null;
×
324
        let nodeStyles: string[];
325

UNCOV
326
        if (!this.isFirefox) {
×
UNCOV
327
            overflow = node.style.overflow;
×
328
            // we need that hack - otherwise content won't be measured correctly in IE/Edge
UNCOV
329
            node.style.overflow = 'visible';
×
330
        }
331

UNCOV
332
        if (sizeHoldingNode) {
×
UNCOV
333
            const style = sizeHoldingNode.style;
×
UNCOV
334
            nodeStyles = [style.width, style.minWidth, style.flexBasis];
×
UNCOV
335
            style.width = '';
×
UNCOV
336
            style.minWidth = '';
×
UNCOV
337
            style.flexBasis = '';
×
338
        }
339

UNCOV
340
        range.selectNodeContents(node);
×
UNCOV
341
        const scale = node.getBoundingClientRect().width / node.offsetWidth;
×
UNCOV
342
        const width = range.getBoundingClientRect().width / scale;
×
343

UNCOV
344
        if (!this.isFirefox) {
×
345
            // we need that hack - otherwise content won't be measured correctly in IE/Edge
UNCOV
346
            node.style.overflow = overflow;
×
347
        }
348

UNCOV
349
        if (sizeHoldingNode) {
×
UNCOV
350
            sizeHoldingNode.style.width = nodeStyles[0];
×
UNCOV
351
            sizeHoldingNode.style.minWidth = nodeStyles[1];
×
UNCOV
352
            sizeHoldingNode.style.flexBasis = nodeStyles[2];
×
353
        }
354

UNCOV
355
        return width;
×
356
    }
357

358

359
    /**
360
     * Returns true if the current keyboard event is an activation key (Enter/Space bar)
361
     *
362
     * @hidden
363
     * @internal
364
     *
365
     * @memberof PlatformUtil
366
     */
367
    public isActivationKey(event: KeyboardEvent) {
UNCOV
368
        return event.key === this.KEYMAP.ENTER || event.key === this.KEYMAP.SPACE;
×
369
    }
370

371
    /**
372
     * Returns true if the current keyboard event is a combination that closes the filtering UI of the grid. (Escape/Ctrl+Shift+L)
373
     *
374
     * @hidden
375
     * @internal
376
     * @param event
377
     * @memberof PlatformUtil
378
     */
379
    public isFilteringKeyCombo(event: KeyboardEvent) {
UNCOV
380
        return event.key === this.KEYMAP.ESCAPE || (event.ctrlKey && event.shiftKey && event.key.toLowerCase() === 'l');
×
381
    }
382

383
    /**
384
     * @hidden @internal
385
     */
386
    public isLeftClick(event: PointerEvent | MouseEvent) {
UNCOV
387
        return event.button === 0;
×
388
    }
389

390
    /**
391
     * @hidden @internal
392
     */
393
    public isNavigationKey(key: string) {
UNCOV
394
        return [
×
395
            this.KEYMAP.HOME, this.KEYMAP.END, this.KEYMAP.SPACE,
396
            this.KEYMAP.ARROW_DOWN, this.KEYMAP.ARROW_LEFT, this.KEYMAP.ARROW_RIGHT, this.KEYMAP.ARROW_UP
397
        ].includes(key as any);
398
    }
399
}
400

401
/**
402
 * @hidden
403
 */
404
export const flatten = (arr: any[]) => {
3✔
UNCOV
405
    let result = [];
×
406

UNCOV
407
    arr.forEach(el => {
×
UNCOV
408
        result.push(el);
×
UNCOV
409
        if (el.children) {
×
UNCOV
410
            const children = Array.isArray(el.children) ? el.children : el.children.toArray();
×
UNCOV
411
            result = result.concat(flatten(children));
×
412
        }
413
    });
UNCOV
414
    return result;
×
415
};
416

417
export interface CancelableEventArgs {
418
    /**
419
     * Provides the ability to cancel the event.
420
     */
421
    cancel: boolean;
422
}
423

424
export interface IBaseEventArgs {
425
    /**
426
     * Provides reference to the owner component.
427
     */
428
    owner?: any;
429
}
430

431
export interface CancelableBrowserEventArgs extends CancelableEventArgs {
432
    /* blazorSuppress */
433
    /** Browser event */
434
    event?: Event;
435
}
436

437
export interface IBaseCancelableBrowserEventArgs extends CancelableBrowserEventArgs, IBaseEventArgs { }
438

439
export interface IBaseCancelableEventArgs extends CancelableEventArgs, IBaseEventArgs { }
440

441
export const NAVIGATION_KEYS = new Set([
3✔
442
    'down',
443
    'up',
444
    'left',
445
    'right',
446
    'arrowdown',
447
    'arrowup',
448
    'arrowleft',
449
    'arrowright',
450
    'home',
451
    'end',
452
    'space',
453
    'spacebar',
454
    ' '
455
]);
456

457
/**
458
 * @hidden
459
 * @internal
460
 *
461
 * Creates a new ResizeObserver on `target` and returns it as an Observable.
462
 * Run the resizeObservable outside angular zone, because it patches the MutationObserver which causes an infinite loop.
463
 * Related issue: https://github.com/angular/angular/issues/31712
464
 */
465
export const resizeObservable = (target: HTMLElement): Observable<ResizeObserverEntry[]> => {
3✔
UNCOV
466
    const resizeObserver = getResizeObserver();
×
467
    // check whether we are on server env or client env
UNCOV
468
    if (resizeObserver) {
×
UNCOV
469
        return new Observable((observer) => {
×
UNCOV
470
            const instance = new resizeObserver((entries: ResizeObserverEntry[]) => {
×
UNCOV
471
                observer.next(entries);
×
472
            });
UNCOV
473
            instance.observe(target);
×
UNCOV
474
            const unsubscribe = () => instance.disconnect();
×
UNCOV
475
            return unsubscribe;
×
476
        });
477
    }
478
    // if on a server env return a empty observable that does not complete immediately
479
    return NEVER;
×
480

481
}
482

483
/**
484
 * @hidden
485
 * @internal
486
 *
487
 * Compares two maps.
488
 */
489
export const compareMaps = (map1: Map<any, any>, map2: Map<any, any>): boolean => {
3✔
UNCOV
490
    if (!map2) {
×
UNCOV
491
        return !map1;
×
492
    }
UNCOV
493
    if (map1.size !== map2.size) {
×
494
        return false;
×
495
    }
UNCOV
496
    let match = true;
×
UNCOV
497
    const keys = Array.from(map2.keys());
×
UNCOV
498
    for (const key of keys) {
×
UNCOV
499
        if (map1.has(key)) {
×
UNCOV
500
            match = map1.get(key) === map2.get(key);
×
501
        } else {
502
            match = false;
×
503
        }
UNCOV
504
        if (!match) {
×
UNCOV
505
            break;
×
506
        }
507
    }
UNCOV
508
    return match;
×
509
};
510

511
function _isObject(entity: unknown): entity is object {
UNCOV
512
    return entity != null && typeof entity === 'object';
×
513
}
514

515
export function columnFieldPath(path?: string): string[] {
UNCOV
516
    return path?.split('.') ?? [];
×
517
}
518

519
/**
520
 * Given a property access path in the format `x.y.z` resolves and returns
521
 * the value of the `z` property in the passed object.
522
 *
523
 * @hidden
524
 * @internal
525
 */
526
export function resolveNestedPath<T extends object, U>(obj: unknown, pathParts: string[], defaultValue?: U): T | U | undefined {
UNCOV
527
    if (!_isObject(obj) || pathParts.length < 1) {
×
UNCOV
528
        return defaultValue;
×
529
    }
530

UNCOV
531
    let current = obj;
×
532

UNCOV
533
    for (const key of pathParts) {
×
UNCOV
534
        if (_isObject(current) && key in (current as T)) {
×
UNCOV
535
            current = current[key];
×
536
        } else {
UNCOV
537
            return defaultValue;
×
538
        }
539
    }
540

UNCOV
541
    return current as T;
×
542
}
543

544
/**
545
 *
546
 * Given a property access path in the format `x.y.z` and a value
547
 * this functions builds and returns an object following the access path.
548
 *
549
 * @example
550
 * ```typescript
551
 * console.log('x.y.z.', 42);
552
 * >> { x: { y: { z: 42 } } }
553
 * ```
554
 *
555
 * @hidden
556
 * @internal
557
 */
558
export const reverseMapper = (path: string, value: any) => {
3✔
UNCOV
559
    const obj = {};
×
UNCOV
560
    const parts = path?.split('.') ?? [];
×
561

UNCOV
562
    let _prop = parts.shift();
×
563
    let mapping: any;
564

565
    // Initial binding for first level bindings
UNCOV
566
    obj[_prop] = value;
×
UNCOV
567
    mapping = obj;
×
568

UNCOV
569
    parts.forEach(prop => {
×
570
        // Start building the hierarchy
UNCOV
571
        mapping[_prop] = {};
×
572
        // Go down a level
UNCOV
573
        mapping = mapping[_prop];
×
574
        // Bind the value and move the key
UNCOV
575
        mapping[prop] = value;
×
UNCOV
576
        _prop = prop;
×
577
    });
578

UNCOV
579
    return obj;
×
580
};
581

582
export const isConstructor = (ref: any) => typeof ref === 'function' && Boolean(ref.prototype) && Boolean(ref.prototype.constructor);
3!
583

584
/** Converts pixel values to their rem counterparts for a base value */
585
export const rem = (value: number | string) => {
3✔
UNCOV
586
    const base = parseFloat(globalThis.window?.getComputedStyle(globalThis.document?.documentElement).getPropertyValue('--ig-base-font-size'))
×
UNCOV
587
    return Number(value) / base;
×
588
}
589

590
/** Get the size of the component as derived from the CSS size variable */
591
export function getComponentSize(el: Element) {
UNCOV
592
    return globalThis.window?.getComputedStyle(el).getPropertyValue('--component-size');
×
593
}
594

595
/** Get the first item in an array */
596
export function first<T>(arr: T[]) {
UNCOV
597
    return arr.at(0) as T;
×
598
}
599

600
/** Get the last item in an array */
601
export function last<T>(arr: T[]) {
UNCOV
602
    return arr.at(-1) as T;
×
603
}
604

605
/** Calculates the modulo of two numbers, ensuring a non-negative result. */
606
export function modulo(n: number, d: number) {
UNCOV
607
    return ((n % d) + d) % d;
×
608
}
609

610
/**
611
 * Splits an array into chunks of length `size` and returns a generator
612
 * yielding each chunk.
613
 * The last chunk may contain less than `size` elements.
614
 *
615
 * @example
616
 * ```typescript
617
 * const arr = [0,1,2,3,4,5,6,7,8,9];
618
 *
619
 * Array.from(chunk(arr, 2)) // [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
620
 * Array.from(chunk(arr, 3)) // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
621
 * Array.from(chunk([], 3)) // []
622
 * Array.from(chunk(arr, -3)) // Error
623
 * ```
624
 */
625
export function* intoChunks<T>(arr: T[], size: number) {
UNCOV
626
    if (size < 1) {
×
627
        throw new Error('size must be an integer >= 1');
×
628
    }
UNCOV
629
    for (let i = 0; i < arr.length; i += size) {
×
UNCOV
630
        yield arr.slice(i, i + size);
×
631
    }
632
}
633

634
/**
635
 * @param size
636
 * @returns string that represents the --component-size default value
637
 */
638
export function getComponentCssSizeVar(size: string) {
639
    switch (size) {
×
640
        case "1":
641
            return 'var(--ig-size, var(--ig-size-small))';
×
642
        case "2":
643
            return 'var(--ig-size, var(--ig-size-medium))';
×
644
        default:
645
            return 'var(--ig-size, var(--ig-size-large))';
×
646
    }
647
}
648

649
/**
650
 * @param path - The URI path to be normalized.
651
 * @returns string encoded using the encodeURI function.
652
 */
653
export function normalizeURI(path: string) {
UNCOV
654
    return path?.split('/').map(encodeURI).join('/');
×
655
}
656

657
export function getComponentTheme(el: Element) {
UNCOV
658
    return globalThis.window
×
659
        ?.getComputedStyle(el)
660
        .getPropertyValue('--theme')
661
        .trim() as IgxTheme;
662
}
663

664
/**
665
 * Collection re-created w/ the built in track by identity will always log
666
 * warning even for valid cases of recalculating all collection items.
667
 * See https://github.com/angular/angular/blob/55581b4181639568fb496e91055142a1b489e988/packages/core/src/render3/instructions/control_flow.ts#L393-L409
668
 * Current solution explicit track function doing the same as suggested in:
669
 * https://github.com/angular/angular/issues/56471#issuecomment-2180315803
670
 * This should be used with moderation and when necessary.
671
 * @internal
672
 */
673
export function trackByIdentity<T>(item: T): T {
UNCOV
674
    return item;
×
675
}
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