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

IgniteUI / igniteui-angular / 20960087204

13 Jan 2026 02:19PM UTC coverage: 12.713% (-78.8%) from 91.5%
20960087204

Pull #16746

github

web-flow
Merge 9afce6e5d into a967f087e
Pull Request #16746: fix(csv): export summaries - master

1008 of 16803 branches covered (6.0%)

19 of 23 new or added lines in 2 files covered. (82.61%)

24693 existing lines in 336 files now uncovered.

3985 of 31345 relevant lines covered (12.71%)

2.49 hits per line

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

0.75
/projects/igniteui-angular/grids/tree-grid/src/tree-grid.component.ts
1
import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, TemplateRef, ContentChild, AfterContentInit, ViewChild, DoCheck, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA, booleanAttribute, inject } from '@angular/core';
2
import { NgClass, NgTemplateOutlet, NgStyle } from '@angular/common';
3
import { IgxTreeGridAPIService } from './tree-grid-api.service';
4
import { IgxGridBaseDirective, IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from 'igniteui-angular/grids/grid';
5
import {
6
    CellType,
7
    GridSelectionMode,
8
    GridType,
9
    IGX_GRID_BASE,
10
    IGX_GRID_SERVICE_BASE,
11
    IgxColumnComponent,
12
    IgxColumnMovingDropDirective,
13
    IgxColumnResizingService,
14
    IgxFilteringService,
15
    IgxGridBodyDirective,
16
    IgxGridCell,
17
    IgxGridColumnResizerComponent,
18
    IgxGridCRUDService,
19
    IgxGridDragSelectDirective,
20
    IgxGridHeaderRowComponent,
21
    IgxGridNavigationService,
22
    IgxGridRowClassesPipe,
23
    IgxGridRowPinningPipe,
24
    IgxGridRowStylesPipe,
25
    IgxGridSelectionService,
26
    IgxGridSummaryService,
27
    IgxGridTransaction,
28
    IgxGridValidationService,
29
    IgxHasVisibleColumnsPipe,
30
    IgxRowEditTabStopDirective,
31
    IgxStringReplacePipe,
32
    IgxSummaryDataPipe,
33
    IgxSummaryRow,
34
    IgxSummaryRowComponent,
35
    IgxTreeGridRow,
36
    IRowDataCancelableEventArgs,
37
    IRowDataEventArgs,
38
    IRowToggleEventArgs,
39
    RowType
40
} from 'igniteui-angular/grids/core';
41
import { first, takeUntil } from 'rxjs/operators';
42
import { IgxRowLoadingIndicatorTemplateDirective } from './tree-grid.directives';
43
import { IgxTreeGridSelectionService } from './tree-grid-selection.service';
44
import { DefaultTreeGridMergeStrategy, HierarchicalState, HierarchicalTransaction, HierarchicalTransactionService, IGridMergeStrategy, IgxHierarchicalTransactionFactory, IgxOverlayOutletDirective, ITreeGridRecord, mergeObjects, StateUpdateEvent, TransactionEventOrigin, TransactionType, TreeGridFilteringStrategy } from 'igniteui-angular/core';
45
import { IgxTreeGridSummaryPipe } from './tree-grid.summary.pipe';
46
import { IgxTreeGridFilteringPipe } from './tree-grid.filtering.pipe';
47
import { IgxTreeGridHierarchizingPipe, IgxTreeGridFlatteningPipe, IgxTreeGridSortingPipe, IgxTreeGridPagingPipe, IgxTreeGridTransactionPipe, IgxTreeGridNormalizeRecordsPipe, IgxTreeGridAddRowPipe } from './tree-grid.pipes';
48
import { IgxTreeGridRowComponent } from './tree-grid-row.component';
49
import { IgxButtonDirective, IgxForOfScrollSyncService, IgxForOfSyncService, IgxGridForOfDirective, IgxRippleDirective, IgxScrollInertiaDirective, IgxTemplateOutletDirective, IgxToggleDirective } from 'igniteui-angular/directives';
50
import { IgxCircularProgressBarComponent } from 'igniteui-angular/progressbar';
51
import { IgxSnackbarComponent } from 'igniteui-angular/snackbar';
52
import { IgxIconComponent } from 'igniteui-angular/icon';
53
import { IgxTreeGridGroupByAreaComponent } from './tree-grid-group-by-area.component';
54

55
let NEXT_ID = 0;
3✔
56

57
/* blazorAdditionalDependency: Column */
58
/* blazorAdditionalDependency: ColumnGroup */
59
/* blazorAdditionalDependency: ColumnLayout */
60
/* blazorAdditionalDependency: GridToolbar */
61
/* blazorAdditionalDependency: GridToolbarActions */
62
/* blazorAdditionalDependency: GridToolbarTitle */
63
/* blazorAdditionalDependency: GridToolbarAdvancedFiltering */
64
/* blazorAdditionalDependency: GridToolbarExporter */
65
/* blazorAdditionalDependency: GridToolbarHiding */
66
/* blazorAdditionalDependency: GridToolbarPinning */
67
/* blazorAdditionalDependency: ActionStrip */
68
/* blazorAdditionalDependency: GridActionsBaseDirective */
69
/* blazorAdditionalDependency: GridEditingActions */
70
/* blazorAdditionalDependency: GridPinningActions */
71
/* blazorIndirectRender */
72
/**
73
 * **Ignite UI for Angular Tree Grid** -
74
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/grid)
75
 *
76
 * The Ignite UI Tree Grid displays and manipulates hierarchical data with consistent schema formatted as a table and
77
 * provides features such as sorting, filtering, editing, column pinning, paging, column moving and hiding.
78
 *
79
 * Example:
80
 * ```html
81
 * <igx-tree-grid [data]="employeeData" primaryKey="employeeID" foreignKey="PID" [autoGenerate]="false">
82
 *   <igx-column field="first" header="First Name"></igx-column>
83
 *   <igx-column field="last" header="Last Name"></igx-column>
84
 *   <igx-column field="role" header="Role"></igx-column>
85
 * </igx-tree-grid>
86
 * ```
87
 */
88
@Component({
89
    changeDetection: ChangeDetectionStrategy.OnPush,
90
    selector: 'igx-tree-grid',
91
    templateUrl: 'tree-grid.component.html',
92
    providers: [
93
        IgxGridCRUDService,
94
        IgxGridValidationService,
95
        IgxGridSummaryService,
96
        IgxGridNavigationService,
97
        { provide: IgxGridSelectionService, useClass: IgxTreeGridSelectionService },
98
        { provide: IGX_GRID_SERVICE_BASE, useClass: IgxTreeGridAPIService },
99
        { provide: IGX_GRID_BASE, useExisting: IgxTreeGridComponent },
100
        IgxFilteringService,
101
        IgxColumnResizingService,
102
        IgxForOfSyncService,
103
        IgxForOfScrollSyncService
104
    ],
105
    imports: [
106
        NgClass,
107
        NgStyle,
108
        NgTemplateOutlet,
109
        IgxGridHeaderRowComponent,
110
        IgxGridBodyDirective,
111
        IgxGridDragSelectDirective,
112
        IgxColumnMovingDropDirective,
113
        IgxGridForOfDirective,
114
        IgxTemplateOutletDirective,
115
        IgxTreeGridRowComponent,
116
        IgxSummaryRowComponent,
117
        IgxOverlayOutletDirective,
118
        IgxToggleDirective,
119
        IgxCircularProgressBarComponent,
120
        IgxSnackbarComponent,
121
        IgxButtonDirective,
122
        IgxRippleDirective,
123
        IgxRowEditTabStopDirective,
124
        IgxIconComponent,
125
        IgxGridColumnResizerComponent,
126
        IgxHasVisibleColumnsPipe,
127
        IgxGridRowPinningPipe,
128
        IgxGridRowClassesPipe,
129
        IgxGridRowStylesPipe,
130
        IgxSummaryDataPipe,
131
        IgxTreeGridHierarchizingPipe,
132
        IgxTreeGridFlatteningPipe,
133
        IgxTreeGridSortingPipe,
134
        IgxTreeGridFilteringPipe,
135
        IgxTreeGridPagingPipe,
136
        IgxTreeGridTransactionPipe,
137
        IgxTreeGridSummaryPipe,
138
        IgxTreeGridNormalizeRecordsPipe,
139
        IgxTreeGridAddRowPipe,
140
        IgxStringReplacePipe,
141
        IgxGridCellMergePipe,
142
        IgxScrollInertiaDirective,
143
        IgxGridUnmergeActivePipe
144
    ],
145
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
146
})
147
export class IgxTreeGridComponent extends IgxGridBaseDirective implements GridType, OnInit, AfterViewInit, DoCheck, AfterContentInit {
3✔
UNCOV
148
    protected override _diTransactions = inject<HierarchicalTransactionService<HierarchicalTransaction, HierarchicalState>>(IgxGridTransaction, { optional: true, });
×
UNCOV
149
    protected override transactionFactory = inject(IgxHierarchicalTransactionFactory);
×
150

151
    /**
152
     * Sets the child data key of the `IgxTreeGridComponent`.
153
     * ```html
154
     * <igx-tree-grid #grid [data]="employeeData" [childDataKey]="'employees'" [autoGenerate]="true"></igx-tree-grid>
155
     * ```
156
     *
157
     * @memberof IgxTreeGridComponent
158
     */
159
    @Input()
160
    public childDataKey: string;
161

162
    /**
163
     * Sets the foreign key of the `IgxTreeGridComponent`.
164
     * ```html
165
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" [autoGenerate]="true">
166
     * </igx-tree-grid>
167
     * ```
168
     *
169
     * @memberof IgxTreeGridComponent
170
     */
171
    @Input()
172
    public foreignKey: string;
173

174
    /**
175
     * Sets the key indicating whether a row has children.
176
     * This property is only used for load on demand scenarios.
177
     * ```html
178
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'"
179
     *                [loadChildrenOnDemand]="loadChildren"
180
     *                [hasChildrenKey]="'hasEmployees'">
181
     * </igx-tree-grid>
182
     * ```
183
     *
184
     * @memberof IgxTreeGridComponent
185
     */
186
    @Input()
187
    public hasChildrenKey: string;
188

189
    /**
190
     * Sets whether child records should be deleted when their parent gets deleted.
191
     * By default it is set to true and deletes all children along with the parent.
192
     * ```html
193
     * <igx-tree-grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" cascadeOnDelete="false">
194
     * </igx-tree-grid>
195
     * ```
196
     *
197
     * @memberof IgxTreeGridComponent
198
     */
199
    @Input({ transform: booleanAttribute })
UNCOV
200
    public cascadeOnDelete = true;
×
201

202
    /* csSuppress */
203
    /**
204
     * Sets a callback for loading child rows on demand.
205
     * ```html
206
     * <igx-tree-grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" [loadChildrenOnDemand]="loadChildren">
207
     * </igx-tree-grid>
208
     * ```
209
     * ```typescript
210
     * public loadChildren = (parentID: any, done: (children: any[]) => void) => {
211
     *     this.dataService.getData(parentID, children => done(children));
212
     * }
213
     * ```
214
     *
215
     * @memberof IgxTreeGridComponent
216
     */
217
    @Input()
218
    public loadChildrenOnDemand: (parentID: any, done: (children: any[]) => void) => void;
219

220
    /**
221
     * @hidden @internal
222
     */
223
    @HostBinding('attr.role')
UNCOV
224
    public role = 'treegrid';
×
225

226
    /**
227
     * Sets the value of the `id` attribute. If not provided it will be automatically generated.
228
     * ```html
229
     * <igx-tree-grid [id]="'igx-tree-grid-1'"></igx-tree-grid>
230
     * ```
231
     *
232
     * @memberof IgxTreeGridComponent
233
     */
234
    @HostBinding('attr.id')
235
    @Input()
UNCOV
236
    public id = `igx-tree-grid-${NEXT_ID++}`;
×
237

238
    /**
239
     * @hidden
240
     * @internal
241
     */
242
    @ContentChild(IgxTreeGridGroupByAreaComponent, { read: IgxTreeGridGroupByAreaComponent })
243
    public treeGroupArea: IgxTreeGridGroupByAreaComponent;
244

245
    /**
246
     * @hidden @internal
247
     */
248
    @ViewChild('record_template', { read: TemplateRef, static: true })
249
    protected recordTemplate: TemplateRef<any>;
250

251
    /**
252
     * @hidden @internal
253
     */
254
    @ViewChild('summary_template', { read: TemplateRef, static: true })
255
    protected summaryTemplate: TemplateRef<any>;
256

257
    /**
258
     * @hidden
259
     */
260
    @ContentChild(IgxRowLoadingIndicatorTemplateDirective, { read: IgxRowLoadingIndicatorTemplateDirective })
261
    protected rowLoadingTemplate: IgxRowLoadingIndicatorTemplateDirective;
262

263
    /**
264
     * @hidden
265
     */
266
    public flatData: any[] | null;
267

268
    /**
269
     * @hidden
270
     */
271
    public processedExpandedFlatData: any[] | null;
272

273
    /**
274
     * Returns an array of the root level `ITreeGridRecord`s.
275
     * ```typescript
276
     * // gets the root record with index=2
277
     * const states = this.grid.rootRecords[2];
278
     * ```
279
     *
280
     * @memberof IgxTreeGridComponent
281
     */
282
    public rootRecords: ITreeGridRecord[];
283

284
    /* blazorSuppress */
285
    /**
286
     * Returns a map of all `ITreeGridRecord`s.
287
     * ```typescript
288
     * // gets the record with primaryKey=2
289
     * const states = this.grid.records.get(2);
290
     * ```
291
     *
292
     * @memberof IgxTreeGridComponent
293
     */
UNCOV
294
    public records: Map<any, ITreeGridRecord> = new Map<any, ITreeGridRecord>();
×
295

296
    /**
297
     * Returns an array of processed (filtered and sorted) root `ITreeGridRecord`s.
298
     * ```typescript
299
     * // gets the processed root record with index=2
300
     * const states = this.grid.processedRootRecords[2];
301
     * ```
302
     *
303
     * @memberof IgxTreeGridComponent
304
     */
305
    public processedRootRecords: ITreeGridRecord[];
306

307
    /* blazorSuppress */
308
    /**
309
     * Returns a map of all processed (filtered and sorted) `ITreeGridRecord`s.
310
     * ```typescript
311
     * // gets the processed record with primaryKey=2
312
     * const states = this.grid.processedRecords.get(2);
313
     * ```
314
     *
315
     * @memberof IgxTreeGridComponent
316
     */
UNCOV
317
    public processedRecords: Map<any, ITreeGridRecord> = new Map<any, ITreeGridRecord>();
×
318

319
    /**
320
     * @hidden
321
     */
UNCOV
322
    public loadingRows = new Set<any>();
×
323

UNCOV
324
    protected override _filterStrategy = new TreeGridFilteringStrategy();
×
325
    protected override _transactions: HierarchicalTransactionService<HierarchicalTransaction, HierarchicalState>;
UNCOV
326
    protected override _mergeStrategy: IGridMergeStrategy = new DefaultTreeGridMergeStrategy();
×
327
    private _data;
328
    private _rowLoadingIndicatorTemplate: TemplateRef<void>;
UNCOV
329
    private _expansionDepth = Infinity;
×
330

331
    /* treatAsRef */
332
    /**
333
     * Gets/Sets the array of data that populates the component.
334
     * ```html
335
     * <igx-tree-grid [data]="Data" [autoGenerate]="true"></igx-tree-grid>
336
     * ```
337
     *
338
     * @memberof IgxTreeGridComponent
339
     */
340
    @Input()
341
    public get data(): any[] | null {
UNCOV
342
        return this._data;
×
343
    }
344

345
    /* treatAsRef */
346
    public set data(value: any[] | null) {
UNCOV
347
        const oldData = this._data;
×
UNCOV
348
        this._data = value || [];
×
UNCOV
349
        this.summaryService.clearSummaryCache();
×
UNCOV
350
        if (!this._init) {
×
UNCOV
351
            this.validation.updateAll(this._data);
×
352
        }
UNCOV
353
        if (this.autoGenerate && this._data.length > 0 && this.shouldRecreateColumns(oldData, this._data)) {
×
UNCOV
354
            this.setupColumns();
×
355
        }
UNCOV
356
        this.checkPrimaryKeyField();
×
UNCOV
357
        this.cdr.markForCheck();
×
358
    }
359

360
    /** @hidden @internal */
361
    public override get type(): GridType["type"] {
UNCOV
362
        return 'tree';
×
363
    }
364

365
    /**
366
     * Get transactions service for the grid.
367
     *
368
     * @experimental @hidden
369
     */
370
    public override get transactions() {
UNCOV
371
        if (this._diTransactions && !this.batchEditing) {
×
UNCOV
372
            return this._diTransactions;
×
373
        }
UNCOV
374
        return this._transactions;
×
375
    }
376

377
    /**
378
     * Sets the count of levels to be expanded in the `IgxTreeGridComponent`. By default it is
379
     * set to `Infinity` which means all levels would be expanded.
380
     * ```html
381
     * <igx-tree-grid #grid [data]="employeeData" [childDataKey]="'employees'" expansionDepth="1" [autoGenerate]="true"></igx-tree-grid>
382
     * ```
383
     *
384
     * @memberof IgxTreeGridComponent
385
     */
386
    @Input()
387
    public get expansionDepth(): number {
UNCOV
388
        return this._expansionDepth;
×
389
    }
390

391
    public set expansionDepth(value: number) {
UNCOV
392
        this._expansionDepth = value;
×
UNCOV
393
        this.notifyChanges();
×
394
    }
395

396
    /**
397
     * Template for the row loading indicator when load on demand is enabled.
398
     * ```html
399
     * <ng-template #rowLoadingTemplate>
400
     *     <igx-icon>loop</igx-icon>
401
     * </ng-template>
402
     *
403
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'ID'" [foreignKey]="'parentID'"
404
     *                [loadChildrenOnDemand]="loadChildren"
405
     *                [rowLoadingIndicatorTemplate]="rowLoadingTemplate">
406
     * </igx-tree-grid>
407
     * ```
408
     *
409
     * @memberof IgxTreeGridComponent
410
     */
411
    @Input()
412
    public get rowLoadingIndicatorTemplate(): TemplateRef<void> {
UNCOV
413
        return this._rowLoadingIndicatorTemplate;
×
414
    }
415

416
    public set rowLoadingIndicatorTemplate(value: TemplateRef<void>) {
417
        this._rowLoadingIndicatorTemplate = value;
×
418
        this.notifyChanges();
×
419
    }
420

421
    // Kind of stupid
422
    // private get _gridAPI(): IgxTreeGridAPIService {
423
    //     return this.gridAPI as IgxTreeGridAPIService;
424
    // }
425

426
    /**
427
     * @hidden
428
     */
429
    public override ngOnInit() {
UNCOV
430
        super.ngOnInit();
×
431

UNCOV
432
        this.rowToggle.pipe(takeUntil(this.destroy$)).subscribe((args) => {
×
UNCOV
433
            this.loadChildrenOnRowExpansion(args);
×
434
        });
435

436
        // TODO: cascade selection logic should be refactor to be handled in the already existing subs
UNCOV
437
        this.rowAddedNotifier.pipe(takeUntil(this.destroy$)).subscribe(args => {
×
UNCOV
438
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
439
                let rec = this.gridAPI.get_rec_by_id(this.primaryKey ? args.data[this.primaryKey] : args.data);
×
UNCOV
440
                if (rec && rec.parent) {
×
UNCOV
441
                    this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
442
                        new Set([rec.parent]), rec.parent.key);
443
                } else {
444
                    // The record is still not available
445
                    // Wait for the change detection to update records through pipes
UNCOV
446
                    requestAnimationFrame(() => {
×
UNCOV
447
                        rec = this.gridAPI.get_rec_by_id(this.primaryKey ?
×
448
                            args.data[this.primaryKey] : args.data);
UNCOV
449
                        if (rec && rec.parent) {
×
UNCOV
450
                            this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
451
                                new Set([rec.parent]), rec.parent.key);
452
                        }
UNCOV
453
                        this.notifyChanges();
×
454
                    });
455
                }
456
            }
457
        });
458

UNCOV
459
        this.rowDeletedNotifier.pipe(takeUntil(this.destroy$)).subscribe(args => {
×
UNCOV
460
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
461
                if (args.data) {
×
UNCOV
462
                    const rec = this.gridAPI.get_rec_by_id(
×
463
                        this.primaryKey ? args.data[this.primaryKey] : args.data);
×
UNCOV
464
                    this.handleCascadeSelection(args, rec);
×
465
                } else {
466
                    // if a row has been added and before commiting the transaction deleted
467
                    const leafRowsDirectParents = new Set<any>();
×
468
                    this.records.forEach(record => {
×
469
                        if (record && (!record.children || record.children.length === 0) && record.parent) {
×
470
                            leafRowsDirectParents.add(record.parent);
×
471
                        }
472
                    });
473
                    // Wait for the change detection to update records through pipes
474
                    requestAnimationFrame(() => {
×
475
                        this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(leafRowsDirectParents);
×
476
                        this.notifyChanges();
×
477
                    });
478
                }
479
            }
480
        });
481

UNCOV
482
        this.filteringDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
483
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
484
                const leafRowsDirectParents = new Set<any>();
×
UNCOV
485
                this.records.forEach(record => {
×
UNCOV
486
                    if (record && (!record.children || record.children.length === 0) && record.parent) {
×
UNCOV
487
                        leafRowsDirectParents.add(record.parent);
×
488
                    }
489
                });
UNCOV
490
                this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(leafRowsDirectParents);
×
UNCOV
491
                this.notifyChanges();
×
492
            }
493
        });
494
    }
495

496
    /**
497
     * @hidden
498
     */
499
    public override ngAfterViewInit() {
UNCOV
500
        super.ngAfterViewInit();
×
501
        // TODO: pipesExectured event
502
        // run after change detection in super triggers pipes for records structure
UNCOV
503
        if (this.rowSelection === GridSelectionMode.multipleCascade && this.selectedRows.length) {
×
504
            const selRows = this.selectedRows;
×
505
            this.selectionService.clearRowSelection();
×
506
            this.selectRows(selRows, true);
×
507
            this.cdr.detectChanges();
×
508
        }
509
    }
510

511
    /**
512
     * @hidden
513
     */
514
    public override ngAfterContentInit() {
UNCOV
515
        if (this.rowLoadingTemplate) {
×
516
            this._rowLoadingIndicatorTemplate = this.rowLoadingTemplate.template;
×
517
        }
UNCOV
518
        super.ngAfterContentInit();
×
519
    }
520

521
    public override getDefaultExpandState(record: ITreeGridRecord): boolean {
522
        return record.children && record.children.length && record.level < this.expansionDepth;
×
523
    }
524

525
    /**
526
     * Expands all rows.
527
     * ```typescript
528
     * this.grid.expandAll();
529
     * ```
530
     *
531
     * @memberof IgxTreeGridComponent
532
     */
533
    public override expandAll() {
UNCOV
534
        this._expansionDepth = Infinity;
×
UNCOV
535
        this.expansionStates = new Map<any, boolean>();
×
536
    }
537

538
    /**
539
     * Collapses all rows.
540
     *
541
     * ```typescript
542
     * this.grid.collapseAll();
543
     *  ```
544
     *
545
     * @memberof IgxTreeGridComponent
546
     */
547
    public override collapseAll() {
UNCOV
548
        this._expansionDepth = 0;
×
UNCOV
549
        this.expansionStates = new Map<any, boolean>();
×
550
    }
551

552
    /**
553
     * @hidden
554
     */
555
    public override refreshGridState(args?: IRowDataEventArgs) {
UNCOV
556
        super.refreshGridState();
×
UNCOV
557
        if (this.primaryKey && this.foreignKey && args) {
×
UNCOV
558
            const rowID = args.data[this.foreignKey];
×
UNCOV
559
            this.summaryService.clearSummaryCache({ rowID });
×
UNCOV
560
            this.pipeTrigger++;
×
UNCOV
561
            this.cdr.detectChanges();
×
562
        }
563
    }
564

565
    /* blazorCSSuppress */
566
    /**
567
     * Creates a new `IgxTreeGridRowComponent` with the given data. If a parentRowID is not specified, the newly created
568
     * row would be added at the root level. Otherwise, it would be added as a child of the row whose primaryKey matches
569
     * the specified parentRowID. If the parentRowID does not exist, an error would be thrown.
570
     * ```typescript
571
     * const record = {
572
     *     ID: this.grid.data[this.grid1.data.length - 1].ID + 1,
573
     *     Name: this.newRecord
574
     * };
575
     * this.grid.addRow(record, 1); // Adds a new child row to the row with ID=1.
576
     * ```
577
     *
578
     * @param data
579
     * @param parentRowID
580
     * @memberof IgxTreeGridComponent
581
     */
582
    // TODO: remove evt emission
583
    public override addRow(data: any, parentRowID?: any) {
UNCOV
584
        this.crudService.endEdit(true);
×
UNCOV
585
        this.gridAPI.addRowToData(data, parentRowID);
×
586

UNCOV
587
        this.rowAddedNotifier.next({
×
588
            data: data,
589
            rowData: data, owner: this,
590
            primaryKey: data[this.primaryKey],
591
            rowKey: data[this.primaryKey]
592
        });
UNCOV
593
        this.pipeTrigger++;
×
UNCOV
594
        this.notifyChanges();
×
595
    }
596

597
    /**
598
     * Enters add mode by spawning the UI with the context of the specified row by index.
599
     *
600
     * @remarks
601
     * Accepted values for index are integers from 0 to this.grid.dataView.length
602
     * @remarks
603
     * When adding the row as a child, the parent row is the specified row.
604
     * @remarks
605
     * To spawn the UI on top, call the function with index = null or a negative number.
606
     * In this case trying to add this row as a child will result in error.
607
     * @example
608
     * ```typescript
609
     * this.grid.beginAddRowByIndex(10);
610
     * this.grid.beginAddRowByIndex(10, true);
611
     * this.grid.beginAddRowByIndex(null);
612
     * ```
613
     * @param index - The index to spawn the UI at. Accepts integers from 0 to this.grid.dataView.length
614
     * @param asChild - Whether the record should be added as a child. Only applicable to igxTreeGrid.
615
     */
616
    public override beginAddRowByIndex(index: number, asChild?: boolean): void {
617
        if (index === null || index < 0) {
×
618
            return this.beginAddRowById(null, asChild);
×
619
        }
620
        return this._addRowForIndex(index - 1, asChild);
×
621
    }
622

623
    /**
624
     * @hidden
625
     */
626
    public getContext(rowData: any, rowIndex: number, pinned?: boolean): any {
UNCOV
627
        return {
×
628
            $implicit: this.isGhostRecord(rowData) || this.isRecordMerged(rowData) ? rowData.recordRef : rowData,
×
629
            index: this.getDataViewIndex(rowIndex, pinned),
630
            templateID: {
631
                type: this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow',
×
632
                id: null
633
            },
634
            disabled: this.isGhostRecord(rowData) ? rowData.recordRef.isFilteredOutParent === undefined : false,
×
635
            metaData: this.isRecordMerged(rowData) ? rowData : null
×
636
        };
637
    }
638

639
    /**
640
     * @hidden
641
     * @internal
642
     */
643
    public override getInitialPinnedIndex(rec) {
UNCOV
644
        const id = this.gridAPI.get_row_id(rec);
×
UNCOV
645
        return this._pinnedRecordIDs.indexOf(id);
×
646
    }
647

648
    /**
649
     * @hidden
650
     * @internal
651
     */
652
    public override isRecordPinned(rec) {
UNCOV
653
        return this.getInitialPinnedIndex(rec.data) !== -1;
×
654
    }
655

656
    /**
657
     *
658
     * Returns an array of the current cell selection in the form of `[{ column.field: cell.value }, ...]`.
659
     *
660
     * @remarks
661
     * If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
662
     * If `headers` is enabled, it will use the column header (if any) instead of the column field.
663
     */
664
    public override getSelectedData(formatters = false, headers = false): any[] {
×
UNCOV
665
        let source = [];
×
666

UNCOV
667
        const process = (record) => {
×
UNCOV
668
            if (record.summaries) {
×
UNCOV
669
                source.push(null);
×
UNCOV
670
                return;
×
671
            }
UNCOV
672
            source.push(record.data);
×
673
        };
674

UNCOV
675
        this.unpinnedDataView.forEach(process);
×
UNCOV
676
        source = this.isRowPinningToTop ? [...this.pinnedDataView, ...source] : [...source, ...this.pinnedDataView];
×
UNCOV
677
        return this.extractDataFromSelection(source, formatters, headers);
×
678
    }
679

680
    /**
681
     * @hidden @internal
682
     */
683
    public override getEmptyRecordObjectFor(inTreeRow: RowType) {
UNCOV
684
        const treeRowRec = inTreeRow?.treeRow || null;
×
UNCOV
685
        const row = { ...treeRowRec };
×
UNCOV
686
        const data = treeRowRec?.data || {};
×
UNCOV
687
        row.data = { ...data };
×
UNCOV
688
        Object.keys(row.data).forEach(key => {
×
689
            // persist foreign key if one is set.
UNCOV
690
            if (this.foreignKey && key === this.foreignKey) {
×
UNCOV
691
                row.data[key] = treeRowRec.data[this.crudService.addRowParent?.asChild ? this.primaryKey : key];
×
692
            } else {
UNCOV
693
                row.data[key] = undefined;
×
694
            }
695
        });
UNCOV
696
        let id = this.generateRowID();
×
UNCOV
697
        const rootRecPK = this.foreignKey && this.rootRecords && this.rootRecords.length > 0 ?
×
698
            this.rootRecords[0].data[this.foreignKey] : null;
UNCOV
699
        if (id === rootRecPK) {
×
700
            // safeguard in case generated id matches the root foreign key.
701
            id = this.generateRowID();
×
702
        }
UNCOV
703
        row.key = id;
×
UNCOV
704
        row.data[this.primaryKey] = id;
×
UNCOV
705
        return { rowID: id, data: row.data, recordRef: row };
×
706
    }
707

708
    /** @hidden */
709
    public override deleteRowById(rowId: any): any {
710
        //  if this is flat self-referencing data, and CascadeOnDelete is set to true
711
        //  and if we have transactions we should start pending transaction. This allows
712
        //  us in case of delete action to delete all child rows as single undo action
UNCOV
713
        const args: IRowDataCancelableEventArgs = {
×
714
            rowID: rowId,
715
            primaryKey: rowId,
716
            rowKey: rowId,
717
            cancel: false,
718
            rowData: this.getRowData(rowId),
719
            data: this.getRowData(rowId),
720
            oldValue: null,
721
            owner: this
722
        };
UNCOV
723
        this.rowDelete.emit(args);
×
UNCOV
724
        if (args.cancel) {
×
UNCOV
725
            return;
×
726
        }
727

UNCOV
728
        const record = this.gridAPI.deleteRowById(rowId);
×
UNCOV
729
        const key = record[this.primaryKey];
×
UNCOV
730
        if (record !== null && record !== undefined) {
×
UNCOV
731
            const rowDeletedEventArgs: IRowDataEventArgs = {
×
732
                data: record,
733
                rowData: record,
734
                owner: this,
735
                primaryKey: key,
736
                rowKey: key
737
            };
UNCOV
738
            this.rowDeleted.emit(rowDeletedEventArgs);
×
739
        }
UNCOV
740
        return record;
×
741
    }
742

743
    /**
744
     * Returns the `IgxTreeGridRow` by index.
745
     *
746
     * @example
747
     * ```typescript
748
     * const myRow = treeGrid.getRowByIndex(1);
749
     * ```
750
     * @param index
751
     */
752
    public getRowByIndex(index: number): RowType {
UNCOV
753
        if (index < 0 || index >= this.dataView.length) {
×
UNCOV
754
            return undefined;
×
755
        }
UNCOV
756
        return this.createRow(index);
×
757
    }
758

759
    /**
760
     * Returns the `RowType` object by the specified primary key.
761
     *
762
     * @example
763
     * ```typescript
764
     * const myRow = this.treeGrid.getRowByIndex(1);
765
     * ```
766
     * @param index
767
     */
768
    public getRowByKey(key: any): RowType {
UNCOV
769
        const rec = this.filteredSortedData ? this.primaryKey ? this.filteredSortedData.find(r => r[this.primaryKey] === key) :
×
UNCOV
770
            this.filteredSortedData.find(r => r === key) : undefined;
×
UNCOV
771
        const index = this.dataView.findIndex(r => r.data && r.data === rec);
×
UNCOV
772
        if (index < 0 || index >= this.filteredSortedData.length) {
×
UNCOV
773
            return undefined;
×
774
        }
UNCOV
775
        return new IgxTreeGridRow(this as any, index, rec);
×
776
    }
777

778
    /**
779
     * Returns the collection of all RowType for current page.
780
     *
781
     * @hidden @internal
782
     */
783
    public allRows(): RowType[] {
UNCOV
784
        return this.dataView.map((rec, index) => this.createRow(index));
×
785
    }
786

787
    /**
788
     * Returns the collection of `IgxTreeGridRow`s for current page.
789
     *
790
     * @hidden @internal
791
     */
792
    public dataRows(): RowType[] {
UNCOV
793
        return this.allRows().filter(row => row instanceof IgxTreeGridRow);
×
794
    }
795

796
    /**
797
     * Returns an array of the selected `IgxGridCell`s.
798
     *
799
     * @example
800
     * ```typescript
801
     * const selectedCells = this.grid.selectedCells;
802
     * ```
803
     */
804
    public get selectedCells(): CellType[] {
UNCOV
805
        return this.dataRows().map((row) => row.cells.filter((cell) => cell.selected))
×
UNCOV
806
            .reduce((a, b) => a.concat(b), []);
×
807
    }
808

809
    /**
810
     * Returns a `CellType` object that matches the conditions.
811
     *
812
     * @example
813
     * ```typescript
814
     * const myCell = this.grid1.getCellByColumn(2, "UnitPrice");
815
     * ```
816
     * @param rowIndex
817
     * @param columnField
818
     */
819
    public getCellByColumn(rowIndex: number, columnField: string): CellType {
UNCOV
820
        const row = this.getRowByIndex(rowIndex);
×
UNCOV
821
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
822
        if (row && row instanceof IgxTreeGridRow && column) {
×
UNCOV
823
            return new IgxGridCell(this as any, rowIndex, column);
×
824
        }
825
    }
826

827
    /**
828
     * Returns a `CellType` object that matches the conditions.
829
     *
830
     * @remarks
831
     * Requires that the primaryKey property is set.
832
     * @example
833
     * ```typescript
834
     * grid.getCellByKey(1, 'index');
835
     * ```
836
     * @param rowSelector match any rowID
837
     * @param columnField
838
     */
839
    public getCellByKey(rowSelector: any, columnField: string): CellType {
UNCOV
840
        const row = this.getRowByKey(rowSelector);
×
UNCOV
841
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
842
        if (row && column) {
×
UNCOV
843
            return new IgxGridCell(this as any, row.index, column);
×
844
        }
845
    }
846

847
    public override pinRow(rowID: any, index?: number): boolean {
UNCOV
848
        const row = this.getRowByKey(rowID);
×
UNCOV
849
        return super.pinRow(rowID, index, row);
×
850
    }
851

852
    public override unpinRow(rowID: any): boolean {
UNCOV
853
        const row = this.getRowByKey(rowID);
×
UNCOV
854
        return super.unpinRow(rowID, row);
×
855
    }
856

857
    /** @hidden */
858
    public generateRowPath(rowId: any): any[] {
UNCOV
859
        const path: any[] = [];
×
UNCOV
860
        let record = this.records.get(rowId);
×
861

UNCOV
862
        while (record.parent) {
×
UNCOV
863
            path.push(record.parent.key);
×
UNCOV
864
            record = record.parent;
×
865
        }
866

UNCOV
867
        return path.reverse();
×
868
    }
869

870
    /** @hidden */
871
    public isTreeRow(record: any): boolean {
UNCOV
872
        return record.key !== undefined && record.data;
×
873
    }
874

875
    /** @hidden */
876
    public override getUnpinnedIndexById(id) {
UNCOV
877
        return this.unpinnedRecords.findIndex(x => x.data[this.primaryKey] === id);
×
878
    }
879

880
    /**
881
     * @hidden
882
     */
883
    public createRow(index: number, data?: any): RowType {
884
        let row: RowType;
UNCOV
885
        const dataIndex = this._getDataViewIndex(index);
×
UNCOV
886
        const rec: any = data ?? this.dataView[dataIndex];
×
887

UNCOV
888
        if (this.isSummaryRow(rec)) {
×
UNCOV
889
            row = new IgxSummaryRow(this as any, index, rec.summaries);
×
890
        }
891

UNCOV
892
        if (!row && rec) {
×
UNCOV
893
            const isTreeRow = this.isTreeRow(rec);
×
UNCOV
894
            const dataRec = isTreeRow ? rec.data : rec;
×
UNCOV
895
            const treeRow = isTreeRow ? rec : undefined;
×
UNCOV
896
            row = new IgxTreeGridRow(this as any, index, dataRec, treeRow);
×
897
        }
898

UNCOV
899
        return row;
×
900
    }
901

902
    protected override generateDataFields(data: any[]): string[] {
UNCOV
903
        return super.generateDataFields(data).filter(field => field !== this.childDataKey);
×
904
    }
905

906
    protected override transactionStatusUpdate(event: StateUpdateEvent) {
UNCOV
907
        let actions = [];
×
UNCOV
908
        if (event.origin === TransactionEventOrigin.REDO) {
×
UNCOV
909
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.DELETE) : [];
×
UNCOV
910
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
911
                this.handleCascadeSelection(event);
×
912
            }
UNCOV
913
        } else if (event.origin === TransactionEventOrigin.UNDO) {
×
UNCOV
914
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.ADD) : [];
×
UNCOV
915
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
916
                if (event.actions[0].transaction.type === 'add') {
×
UNCOV
917
                    const rec = this.gridAPI.get_rec_by_id(event.actions[0].transaction.id);
×
UNCOV
918
                    this.handleCascadeSelection(event, rec);
×
919
                } else {
UNCOV
920
                    this.handleCascadeSelection(event);
×
921
                }
922
            }
923
        }
UNCOV
924
        if (actions.length) {
×
UNCOV
925
            for (const action of actions) {
×
UNCOV
926
                this.deselectChildren(action.transaction.id);
×
927
            }
928
        }
UNCOV
929
        super.transactionStatusUpdate(event);
×
930
    }
931

932
    protected findRecordIndexInView(rec) {
933
        return this.dataView.findIndex(x => x.data[this.primaryKey] === rec[this.primaryKey]);
×
934
    }
935

936
    /**
937
     * @hidden @internal
938
     */
939
    protected override getDataBasedBodyHeight(): number {
UNCOV
940
        return !this.flatData || (this.flatData.length < this._defaultTargetRecordNumber) ?
×
941
            0 : this.defaultTargetBodyHeight;
942
    }
943

944
    /**
945
     * @hidden
946
     */
947
    protected override scrollTo(row: any | number, column: any | number): void {
UNCOV
948
        let delayScrolling = false;
×
949
        let record: ITreeGridRecord;
950

UNCOV
951
        if (typeof (row) !== 'number') {
×
UNCOV
952
            const rowData = row;
×
UNCOV
953
            const rowID = this.gridAPI.get_row_id(rowData);
×
UNCOV
954
            record = this.processedRecords.get(rowID);
×
UNCOV
955
            this.gridAPI.expand_path_to_record(record);
×
956

UNCOV
957
            if (this.paginator) {
×
UNCOV
958
                const rowIndex = this.processedExpandedFlatData.indexOf(rowData);
×
UNCOV
959
                const page = Math.floor(rowIndex / this.perPage);
×
960

UNCOV
961
                if (this.page !== page) {
×
UNCOV
962
                    delayScrolling = true;
×
UNCOV
963
                    this.page = page;
×
964
                }
965
            }
966
        }
967

UNCOV
968
        if (delayScrolling) {
×
UNCOV
969
            this.verticalScrollContainer.dataChanged.pipe(first()).subscribe(() => {
×
UNCOV
970
                this.scrollDirective(this.verticalScrollContainer,
×
971
                    typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(record));
×
972
            });
973
        } else {
UNCOV
974
            this.scrollDirective(this.verticalScrollContainer,
×
975
                typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(record));
×
976
        }
977

UNCOV
978
        this.scrollToHorizontally(column);
×
979
    }
980

981
    protected override writeToData(rowIndex: number, value: any) {
982
        mergeObjects(this.flatData[rowIndex], value);
×
983
    }
984

985
    /**
986
     * @hidden
987
     */
988
    protected override initColumns(collection: IgxColumnComponent[], cb: (args: any) => void = null) {
×
UNCOV
989
        if (this.hasColumnLayouts) {
×
990
            // invalid configuration - tree grid should not allow column layouts
991
            // remove column layouts
992
            const nonColumnLayoutColumns = this.columns.filter((col) => !col.columnLayout && !col.columnLayoutChild);
×
993
            this.updateColumns(nonColumnLayoutColumns);
×
994
        }
UNCOV
995
        super.initColumns(collection, cb);
×
996
    }
997

998
    /**
999
     * @hidden @internal
1000
     */
1001
    protected override getGroupAreaHeight(): number {
UNCOV
1002
        return this.treeGroupArea ? this.getComputedHeight(this.treeGroupArea.nativeElement) : 0;
×
1003
    }
1004

1005
    /** {@link triggerPipes} will re-create pinnedData on CRUD operations */
1006
    protected trackPinnedRowData(record: ITreeGridRecord) {
1007
        // TODO FIX: pipeline data doesn't match end interface (¬_¬ )
1008
        // return record.key || (record as any).rowID;
UNCOV
1009
        return record;
×
1010
    }
1011

1012
    /**
1013
     * @description A recursive way to deselect all selected children of a given record
1014
     * @param recordID ID of the record whose children to deselect
1015
     * @hidden
1016
     * @internal
1017
     */
1018
    private deselectChildren(recordID): void {
UNCOV
1019
        const selectedChildren = [];
×
1020
        // G.E. Apr 28, 2021 #9465 Records which are not in view can also be selected so we need to
1021
        // deselect them as well, hence using 'records' map instead of getRowByKey() method which will
1022
        // return only row components (i.e. records in view).
UNCOV
1023
        const rowToDeselect = this.records.get(recordID);
×
UNCOV
1024
        this.selectionService.deselectRowsWithNoEvent([recordID]);
×
UNCOV
1025
        this.gridAPI.get_selected_children(rowToDeselect, selectedChildren);
×
UNCOV
1026
        if (selectedChildren.length > 0) {
×
UNCOV
1027
            selectedChildren.forEach(x => this.deselectChildren(x));
×
1028
        }
1029
    }
1030

1031
    private addChildRows(children: any[], parentID: any) {
UNCOV
1032
        if (this.primaryKey && this.foreignKey) {
×
UNCOV
1033
            for (const child of children) {
×
UNCOV
1034
                child[this.foreignKey] = parentID;
×
1035
            }
UNCOV
1036
            this.data.push(...children);
×
UNCOV
1037
        } else if (this.childDataKey) {
×
UNCOV
1038
            let parent = this.records.get(parentID);
×
UNCOV
1039
            let parentData = parent.data;
×
1040

UNCOV
1041
            if (this.transactions.enabled && this.transactions.getAggregatedChanges(true).length) {
×
1042
                const path = [];
×
1043
                while (parent) {
×
1044
                    path.push(parent.key);
×
1045
                    parent = parent.parent;
×
1046
                }
1047

1048
                let collection = this.data;
×
1049
                let record: any;
1050
                for (let i = path.length - 1; i >= 0; i--) {
×
1051
                    const pid = path[i];
×
1052
                    record = collection.find(r => r[this.primaryKey] === pid);
×
1053

1054
                    if (!record) {
×
1055
                        break;
×
1056
                    }
1057
                    collection = record[this.childDataKey];
×
1058
                }
1059
                if (record) {
×
1060
                    parentData = record;
×
1061
                }
1062
            }
1063

UNCOV
1064
            parentData[this.childDataKey] = children;
×
1065
        }
UNCOV
1066
        this.selectionService.clearHeaderCBState();
×
UNCOV
1067
        this.pipeTrigger++;
×
UNCOV
1068
        if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
1069
            // Force pipe triggering for building the data structure
UNCOV
1070
            this.cdr.detectChanges();
×
UNCOV
1071
            if (this.selectionService.isRowSelected(parentID)) {
×
UNCOV
1072
                this.selectionService.rowSelection.delete(parentID);
×
UNCOV
1073
                this.selectionService.selectRowsWithNoEvent([parentID]);
×
1074
            }
1075
        }
1076
    }
1077

1078
    private loadChildrenOnRowExpansion(args: IRowToggleEventArgs) {
UNCOV
1079
        if (this.loadChildrenOnDemand) {
×
UNCOV
1080
            const parentID = args.rowID;
×
1081

UNCOV
1082
            if (args.expanded && !this._expansionStates.has(parentID)) {
×
UNCOV
1083
                this.loadingRows.add(parentID);
×
1084

UNCOV
1085
                this.loadChildrenOnDemand(parentID, children => {
×
UNCOV
1086
                    this.loadingRows.delete(parentID);
×
UNCOV
1087
                    this.addChildRows(children, parentID);
×
UNCOV
1088
                    this.notifyChanges();
×
1089
                });
1090
            }
1091
        }
1092
    }
1093

1094
    private handleCascadeSelection(event: IRowDataEventArgs | StateUpdateEvent, rec: ITreeGridRecord = null) {
×
1095
        // Wait for the change detection to update records through the pipes
UNCOV
1096
        requestAnimationFrame(() => {
×
UNCOV
1097
            if (rec === null) {
×
UNCOV
1098
                rec = this.gridAPI.get_rec_by_id((event as StateUpdateEvent).actions[0].transaction.id);
×
1099
            }
UNCOV
1100
            if (rec && rec.parent) {
×
UNCOV
1101
                this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
1102
                    new Set([rec.parent]), rec.parent.key
1103
                );
UNCOV
1104
                this.notifyChanges();
×
1105
            }
1106
        });
1107
    }
1108
}
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