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

IgniteUI / igniteui-angular / 16053471080

03 Jul 2025 02:41PM UTC coverage: 4.981% (-86.4%) from 91.409%
16053471080

Pull #16021

github

web-flow
Merge 7c49966eb into 7e40671a1
Pull Request #16021: fix(radio-group): dynamically added radio buttons do not initialize

178 of 15753 branches covered (1.13%)

13 of 14 new or added lines in 2 files covered. (92.86%)

25644 existing lines in 324 files now uncovered.

1478 of 29670 relevant lines covered (4.98%)

0.51 hits per line

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

0.75
/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts
1
import {
2
    ChangeDetectionStrategy,
3
    Component,
4
    HostBinding,
5
    Input,
6
    OnInit,
7
    TemplateRef,
8
    ContentChild,
9
    AfterContentInit,
10
    ViewChild,
11
    DoCheck,
12
    AfterViewInit,
13
    ElementRef,
14
    NgZone,
15
    Inject,
16
    ChangeDetectorRef,
17
    IterableDiffers,
18
    ViewContainerRef,
19
    Optional,
20
    LOCALE_ID,
21
    Injector,
22
    EnvironmentInjector,
23
    CUSTOM_ELEMENTS_SCHEMA,
24
    booleanAttribute,
25
    DOCUMENT
26
} from '@angular/core';
27
import { NgClass, NgTemplateOutlet, NgStyle } from '@angular/common';
28

29
import { IgxTreeGridAPIService } from './tree-grid-api.service';
30
import { IgxGridBaseDirective } from '../grid-base.directive';
31
import { ITreeGridRecord } from './tree-grid.interfaces';
32
import { IRowDataCancelableEventArgs, IRowDataEventArgs, IRowToggleEventArgs } from '../common/events';
33
import {
34
    HierarchicalTransaction,
35
    HierarchicalState,
36
    TransactionType,
37
    TransactionEventOrigin,
38
    StateUpdateEvent
39
} from '../../services/transaction/transaction';
40
import { IgxFilteringService } from '../filtering/grid-filtering.service';
41
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
42
import { IgxGridSelectionService } from '../selection/selection.service';
43
import { mergeObjects, PlatformUtil } from '../../core/utils';
44
import { first, takeUntil } from 'rxjs/operators';
45
import { IgxRowLoadingIndicatorTemplateDirective } from './tree-grid.directives';
46
import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives/for-of/for_of.sync.service';
47
import { IgxGridNavigationService } from '../grid-navigation.service';
48
import { CellType, GridServiceType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface';
49
import { IgxColumnComponent } from '../columns/column.component';
50
import { IgxTreeGridSelectionService } from './tree-grid-selection.service';
51
import { GridSelectionMode } from '../common/enums';
52
import { IgxSummaryRow, IgxTreeGridRow } from '../grid-public-row';
53
import { IgxGridCRUDService } from '../common/crud.service';
54
import { IgxTreeGridGroupByAreaComponent } from '../grouping/tree-grid-group-by-area.component';
55
import { IgxGridCell } from '../grid-public-cell';
56
import { IgxHierarchicalTransactionFactory } from '../../services/transaction/transaction-factory.service';
57
import { IgxColumnResizingService } from '../resizing/resizing.service';
58
import { HierarchicalTransactionService } from '../../services/transaction/hierarchical-transaction';
59
import { IgxOverlayService } from '../../services/overlay/overlay';
60
import { IgxGridTransaction } from '../common/types';
61
import { TreeGridFilteringStrategy } from './tree-grid.filtering.strategy';
62
import { IgxGridValidationService } from '../grid/grid-validation.service';
63
import { IgxTreeGridSummaryPipe } from './tree-grid.summary.pipe';
64
import { IgxTreeGridFilteringPipe } from './tree-grid.filtering.pipe';
65
import { IgxTreeGridHierarchizingPipe, IgxTreeGridFlatteningPipe, IgxTreeGridSortingPipe, IgxTreeGridPagingPipe, IgxTreeGridTransactionPipe, IgxTreeGridNormalizeRecordsPipe, IgxTreeGridAddRowPipe } from './tree-grid.pipes';
66
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
67
import { IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
68
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
69
import { IgxIconComponent } from '../../icon/icon.component';
70
import { IgxRowEditTabStopDirective } from '../grid.rowEdit.directive';
71
import { IgxRippleDirective } from '../../directives/ripple/ripple.directive';
72
import { IgxButtonDirective } from '../../directives/button/button.directive';
73
import { IgxSnackbarComponent } from '../../snackbar/snackbar.component';
74
import { IgxCircularProgressBarComponent } from '../../progressbar/progressbar.component';
75
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../../directives/toggle/toggle.directive';
76
import { IgxSummaryRowComponent } from '../summaries/summary-row.component';
77
import { IgxTreeGridRowComponent } from './tree-grid-row.component';
78
import { IgxTemplateOutletDirective } from '../../directives/template-outlet/template_outlet.directive';
79
import { IgxGridForOfDirective } from '../../directives/for-of/for_of.directive';
80
import { IgxColumnMovingDropDirective } from '../moving/moving.drop.directive';
81
import { IgxGridDragSelectDirective } from '../selection/drag-select.directive';
82
import { IgxGridBodyDirective } from '../grid.common';
83
import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component';
84
import { IgxTextHighlightService } from '../../directives/text-highlight/text-highlight.service';
85

86
let NEXT_ID = 0;
3✔
87

88
/* blazorAdditionalDependency: Column */
89
/* blazorAdditionalDependency: ColumnGroup */
90
/* blazorAdditionalDependency: ColumnLayout */
91
/* blazorAdditionalDependency: GridToolbar */
92
/* blazorAdditionalDependency: GridToolbarActions */
93
/* blazorAdditionalDependency: GridToolbarTitle */
94
/* blazorAdditionalDependency: GridToolbarAdvancedFiltering */
95
/* blazorAdditionalDependency: GridToolbarExporter */
96
/* blazorAdditionalDependency: GridToolbarHiding */
97
/* blazorAdditionalDependency: GridToolbarPinning */
98
/* blazorAdditionalDependency: ActionStrip */
99
/* blazorAdditionalDependency: GridActionsBaseDirective */
100
/* blazorAdditionalDependency: GridEditingActions */
101
/* blazorAdditionalDependency: GridPinningActions */
102
/* blazorIndirectRender */
103
/**
104
 * **Ignite UI for Angular Tree Grid** -
105
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/grid/grid)
106
 *
107
 * The Ignite UI Tree Grid displays and manipulates hierarchical data with consistent schema formatted as a table and
108
 * provides features such as sorting, filtering, editing, column pinning, paging, column moving and hiding.
109
 *
110
 * Example:
111
 * ```html
112
 * <igx-tree-grid [data]="employeeData" primaryKey="employeeID" foreignKey="PID" [autoGenerate]="false">
113
 *   <igx-column field="first" header="First Name"></igx-column>
114
 *   <igx-column field="last" header="Last Name"></igx-column>
115
 *   <igx-column field="role" header="Role"></igx-column>
116
 * </igx-tree-grid>
117
 * ```
118
 */
119
@Component({
120
    changeDetection: ChangeDetectionStrategy.OnPush,
121
    selector: 'igx-tree-grid',
122
    templateUrl: 'tree-grid.component.html',
123
    providers: [
124
        IgxGridCRUDService,
125
        IgxGridValidationService,
126
        IgxGridSummaryService,
127
        IgxGridNavigationService,
128
        { provide: IgxGridSelectionService, useClass: IgxTreeGridSelectionService },
129
        { provide: IGX_GRID_SERVICE_BASE, useClass: IgxTreeGridAPIService },
130
        { provide: IGX_GRID_BASE, useExisting: IgxTreeGridComponent },
131
        IgxFilteringService,
132
        IgxColumnResizingService,
133
        IgxForOfSyncService,
134
        IgxForOfScrollSyncService
135
    ],
136
    imports: [
137
        NgClass,
138
        NgStyle,
139
        NgTemplateOutlet,
140
        IgxGridHeaderRowComponent,
141
        IgxGridBodyDirective,
142
        IgxGridDragSelectDirective,
143
        IgxColumnMovingDropDirective,
144
        IgxGridForOfDirective,
145
        IgxTemplateOutletDirective,
146
        IgxTreeGridRowComponent,
147
        IgxSummaryRowComponent,
148
        IgxOverlayOutletDirective,
149
        IgxToggleDirective,
150
        IgxCircularProgressBarComponent,
151
        IgxSnackbarComponent,
152
        IgxButtonDirective,
153
        IgxRippleDirective,
154
        IgxRowEditTabStopDirective,
155
        IgxIconComponent,
156
        IgxGridColumnResizerComponent,
157
        IgxHasVisibleColumnsPipe,
158
        IgxGridRowPinningPipe,
159
        IgxGridRowClassesPipe,
160
        IgxGridRowStylesPipe,
161
        IgxSummaryDataPipe,
162
        IgxTreeGridHierarchizingPipe,
163
        IgxTreeGridFlatteningPipe,
164
        IgxTreeGridSortingPipe,
165
        IgxTreeGridFilteringPipe,
166
        IgxTreeGridPagingPipe,
167
        IgxTreeGridTransactionPipe,
168
        IgxTreeGridSummaryPipe,
169
        IgxTreeGridNormalizeRecordsPipe,
170
        IgxTreeGridAddRowPipe,
171
        IgxStringReplacePipe
172
    ],
173
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
174
})
175
export class IgxTreeGridComponent extends IgxGridBaseDirective implements GridType, OnInit, AfterViewInit, DoCheck, AfterContentInit {
3✔
176
    /**
177
     * Sets the child data key of the `IgxTreeGridComponent`.
178
     * ```html
179
     * <igx-tree-grid #grid [data]="employeeData" [childDataKey]="'employees'" [autoGenerate]="true"></igx-tree-grid>
180
     * ```
181
     *
182
     * @memberof IgxTreeGridComponent
183
     */
184
    @Input()
185
    public childDataKey: string;
186

187
    /**
188
     * Sets the foreign key of the `IgxTreeGridComponent`.
189
     * ```html
190
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" [autoGenerate]="true">
191
     * </igx-tree-grid>
192
     * ```
193
     *
194
     * @memberof IgxTreeGridComponent
195
     */
196
    @Input()
197
    public foreignKey: string;
198

199
    /**
200
     * Sets the key indicating whether a row has children.
201
     * This property is only used for load on demand scenarios.
202
     * ```html
203
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'"
204
     *                [loadChildrenOnDemand]="loadChildren"
205
     *                [hasChildrenKey]="'hasEmployees'">
206
     * </igx-tree-grid>
207
     * ```
208
     *
209
     * @memberof IgxTreeGridComponent
210
     */
211
    @Input()
212
    public hasChildrenKey: string;
213

214
    /**
215
     * Sets whether child records should be deleted when their parent gets deleted.
216
     * By default it is set to true and deletes all children along with the parent.
217
     * ```html
218
     * <igx-tree-grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" cascadeOnDelete="false">
219
     * </igx-tree-grid>
220
     * ```
221
     *
222
     * @memberof IgxTreeGridComponent
223
     */
224
    @Input({ transform: booleanAttribute })
UNCOV
225
    public cascadeOnDelete = true;
×
226

227
    /* csSuppress */
228
    /**
229
     * Sets a callback for loading child rows on demand.
230
     * ```html
231
     * <igx-tree-grid [data]="employeeData" [primaryKey]="'employeeID'" [foreignKey]="'parentID'" [loadChildrenOnDemand]="loadChildren">
232
     * </igx-tree-grid>
233
     * ```
234
     * ```typescript
235
     * public loadChildren = (parentID: any, done: (children: any[]) => void) => {
236
     *     this.dataService.getData(parentID, children => done(children));
237
     * }
238
     * ```
239
     *
240
     * @memberof IgxTreeGridComponent
241
     */
242
    @Input()
243
    public loadChildrenOnDemand: (parentID: any, done: (children: any[]) => void) => void;
244

245
    /**
246
     * @hidden @internal
247
     */
248
    @HostBinding('attr.role')
UNCOV
249
    public role = 'treegrid';
×
250

251
    /**
252
     * Sets the value of the `id` attribute. If not provided it will be automatically generated.
253
     * ```html
254
     * <igx-tree-grid [id]="'igx-tree-grid-1'"></igx-tree-grid>
255
     * ```
256
     *
257
     * @memberof IgxTreeGridComponent
258
     */
259
    @HostBinding('attr.id')
260
    @Input()
UNCOV
261
    public id = `igx-tree-grid-${NEXT_ID++}`;
×
262

263
    /**
264
     * @hidden
265
     * @internal
266
     */
267
    @ContentChild(IgxTreeGridGroupByAreaComponent, { read: IgxTreeGridGroupByAreaComponent })
268
    public treeGroupArea: IgxTreeGridGroupByAreaComponent;
269

270
    /**
271
     * @hidden @internal
272
     */
273
    @ViewChild('record_template', { read: TemplateRef, static: true })
274
    protected recordTemplate: TemplateRef<any>;
275

276
    /**
277
     * @hidden @internal
278
     */
279
    @ViewChild('summary_template', { read: TemplateRef, static: true })
280
    protected summaryTemplate: TemplateRef<any>;
281

282
    /**
283
     * @hidden
284
     */
285
    @ContentChild(IgxRowLoadingIndicatorTemplateDirective, { read: IgxRowLoadingIndicatorTemplateDirective })
286
    protected rowLoadingTemplate: IgxRowLoadingIndicatorTemplateDirective;
287

288
    /**
289
     * @hidden
290
     */
291
    public flatData: any[] | null;
292

293
    /**
294
     * @hidden
295
     */
296
    public processedExpandedFlatData: any[] | null;
297

298
    /**
299
     * Returns an array of the root level `ITreeGridRecord`s.
300
     * ```typescript
301
     * // gets the root record with index=2
302
     * const states = this.grid.rootRecords[2];
303
     * ```
304
     *
305
     * @memberof IgxTreeGridComponent
306
     */
307
    public rootRecords: ITreeGridRecord[];
308

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

321
    /**
322
     * Returns an array of processed (filtered and sorted) root `ITreeGridRecord`s.
323
     * ```typescript
324
     * // gets the processed root record with index=2
325
     * const states = this.grid.processedRootRecords[2];
326
     * ```
327
     *
328
     * @memberof IgxTreeGridComponent
329
     */
330
    public processedRootRecords: ITreeGridRecord[];
331

332
    /* blazorSuppress */
333
    /**
334
     * Returns a map of all processed (filtered and sorted) `ITreeGridRecord`s.
335
     * ```typescript
336
     * // gets the processed record with primaryKey=2
337
     * const states = this.grid.processedRecords.get(2);
338
     * ```
339
     *
340
     * @memberof IgxTreeGridComponent
341
     */
UNCOV
342
    public processedRecords: Map<any, ITreeGridRecord> = new Map<any, ITreeGridRecord>();
×
343

344
    /**
345
     * @hidden
346
     */
UNCOV
347
    public loadingRows = new Set<any>();
×
348

UNCOV
349
    protected override _filterStrategy = new TreeGridFilteringStrategy();
×
350
    protected override _transactions: HierarchicalTransactionService<HierarchicalTransaction, HierarchicalState>;
351
    private _data;
352
    private _rowLoadingIndicatorTemplate: TemplateRef<void>;
UNCOV
353
    private _expansionDepth = Infinity;
×
354

355
     /* treatAsRef */
356
    /**
357
     * Gets/Sets the array of data that populates the component.
358
     * ```html
359
     * <igx-tree-grid [data]="Data" [autoGenerate]="true"></igx-tree-grid>
360
     * ```
361
     *
362
     * @memberof IgxTreeGridComponent
363
     */
364
    @Input()
365
    public get data(): any[] | null {
UNCOV
366
        return this._data;
×
367
    }
368

369
     /* treatAsRef */
370
    public set data(value: any[] | null) {
UNCOV
371
        const oldData = this._data;
×
UNCOV
372
        this._data = value || [];
×
UNCOV
373
        this.summaryService.clearSummaryCache();
×
UNCOV
374
        if (!this._init) {
×
UNCOV
375
            this.validation.updateAll(this._data);
×
376
        }
UNCOV
377
        if (this.autoGenerate && this._data.length > 0 && this.shouldRecreateColumns(oldData, this._data)) {
×
UNCOV
378
            this.setupColumns();
×
379
        }
UNCOV
380
        this.checkPrimaryKeyField();
×
UNCOV
381
        this.cdr.markForCheck();
×
382
    }
383

384
    /** @hidden @internal */
385
    public override get type(): GridType["type"] {
UNCOV
386
        return 'tree';
×
387
    }
388

389
    /**
390
     * Get transactions service for the grid.
391
     *
392
     * @experimental @hidden
393
     */
394
    public override get transactions() {
UNCOV
395
        if (this._diTransactions && !this.batchEditing) {
×
UNCOV
396
            return this._diTransactions;
×
397
        }
UNCOV
398
        return this._transactions;
×
399
    }
400

401
    /**
402
     * Sets the count of levels to be expanded in the `IgxTreeGridComponent`. By default it is
403
     * set to `Infinity` which means all levels would be expanded.
404
     * ```html
405
     * <igx-tree-grid #grid [data]="employeeData" [childDataKey]="'employees'" expansionDepth="1" [autoGenerate]="true"></igx-tree-grid>
406
     * ```
407
     *
408
     * @memberof IgxTreeGridComponent
409
     */
410
    @Input()
411
    public get expansionDepth(): number {
UNCOV
412
        return this._expansionDepth;
×
413
    }
414

415
    public set expansionDepth(value: number) {
UNCOV
416
        this._expansionDepth = value;
×
UNCOV
417
        this.notifyChanges();
×
418
    }
419

420
    /**
421
     * Template for the row loading indicator when load on demand is enabled.
422
     * ```html
423
     * <ng-template #rowLoadingTemplate>
424
     *     <igx-icon>loop</igx-icon>
425
     * </ng-template>
426
     *
427
     * <igx-tree-grid #grid [data]="employeeData" [primaryKey]="'ID'" [foreignKey]="'parentID'"
428
     *                [loadChildrenOnDemand]="loadChildren"
429
     *                [rowLoadingIndicatorTemplate]="rowLoadingTemplate">
430
     * </igx-tree-grid>
431
     * ```
432
     *
433
     * @memberof IgxTreeGridComponent
434
     */
435
    @Input()
436
    public get rowLoadingIndicatorTemplate(): TemplateRef<void> {
UNCOV
437
        return this._rowLoadingIndicatorTemplate;
×
438
    }
439

440
    public set rowLoadingIndicatorTemplate(value: TemplateRef<void>) {
441
        this._rowLoadingIndicatorTemplate = value;
×
442
        this.notifyChanges();
×
443
    }
444

445
    // Kind of stupid
446
    // private get _gridAPI(): IgxTreeGridAPIService {
447
    //     return this.gridAPI as IgxTreeGridAPIService;
448
    // }
449

450
    constructor(
451
        validationService: IgxGridValidationService,
452
        selectionService: IgxGridSelectionService,
453
        colResizingService: IgxColumnResizingService,
454
        @Inject(IGX_GRID_SERVICE_BASE) gridAPI: GridServiceType,
455
        // public gridAPI: GridBaseAPIService<IgxGridBaseDirective & GridType>,
456
        transactionFactory: IgxHierarchicalTransactionFactory,
457
        _elementRef: ElementRef<HTMLElement>,
458
        _zone: NgZone,
459
        @Inject(DOCUMENT) document: any,
460
        cdr: ChangeDetectorRef,
461
        differs: IterableDiffers,
462
        viewRef: ViewContainerRef,
463
        injector: Injector,
464
        envInjector: EnvironmentInjector,
465
        navigation: IgxGridNavigationService,
466
        filteringService: IgxFilteringService,
467
        textHighlightService: IgxTextHighlightService,
468
        @Inject(IgxOverlayService) overlayService: IgxOverlayService,
469
        summaryService: IgxGridSummaryService,
470
        @Inject(LOCALE_ID) localeId: string,
471
        platform: PlatformUtil,
UNCOV
472
        @Optional() @Inject(IgxGridTransaction) protected override _diTransactions?:
×
473
            HierarchicalTransactionService<HierarchicalTransaction, HierarchicalState>,
474
    ) {
UNCOV
475
        super(
×
476
            validationService,
477
            selectionService,
478
            colResizingService,
479
            gridAPI,
480
            transactionFactory,
481
            _elementRef,
482
            _zone,
483
            document,
484
            cdr,
485
            differs,
486
            viewRef,
487
            injector,
488
            envInjector,
489
            navigation,
490
            filteringService,
491
            textHighlightService,
492
            overlayService,
493
            summaryService,
494
            localeId,
495
            platform,
496
            _diTransactions,
497
        );
498
    }
499

500
    /**
501
     * @hidden
502
     */
503
    public override ngOnInit() {
UNCOV
504
        super.ngOnInit();
×
505

UNCOV
506
        this.rowToggle.pipe(takeUntil(this.destroy$)).subscribe((args) => {
×
UNCOV
507
            this.loadChildrenOnRowExpansion(args);
×
508
        });
509

510
        // TODO: cascade selection logic should be refactor to be handled in the already existing subs
UNCOV
511
        this.rowAddedNotifier.pipe(takeUntil(this.destroy$)).subscribe(args => {
×
UNCOV
512
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
513
                let rec = this.gridAPI.get_rec_by_id(this.primaryKey ? args.data[this.primaryKey] : args.data);
×
UNCOV
514
                if (rec && rec.parent) {
×
UNCOV
515
                    this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
516
                        new Set([rec.parent]), rec.parent.key);
517
                } else {
518
                    // The record is still not available
519
                    // Wait for the change detection to update records through pipes
UNCOV
520
                    requestAnimationFrame(() => {
×
UNCOV
521
                        rec = this.gridAPI.get_rec_by_id(this.primaryKey ?
×
522
                            args.data[this.primaryKey] : args.data);
UNCOV
523
                        if (rec && rec.parent) {
×
UNCOV
524
                            this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
525
                                new Set([rec.parent]), rec.parent.key);
526
                        }
UNCOV
527
                        this.notifyChanges();
×
528
                    });
529
                }
530
            }
531
        });
532

UNCOV
533
        this.rowDeletedNotifier.pipe(takeUntil(this.destroy$)).subscribe(args => {
×
UNCOV
534
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
535
                if (args.data) {
×
UNCOV
536
                    const rec = this.gridAPI.get_rec_by_id(
×
537
                        this.primaryKey ? args.data[this.primaryKey] : args.data);
×
UNCOV
538
                    this.handleCascadeSelection(args, rec);
×
539
                } else {
540
                    // if a row has been added and before commiting the transaction deleted
541
                    const leafRowsDirectParents = new Set<any>();
×
542
                    this.records.forEach(record => {
×
543
                        if (record && (!record.children || record.children.length === 0) && record.parent) {
×
544
                            leafRowsDirectParents.add(record.parent);
×
545
                        }
546
                    });
547
                    // Wait for the change detection to update records through pipes
548
                    requestAnimationFrame(() => {
×
549
                        this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(leafRowsDirectParents);
×
550
                        this.notifyChanges();
×
551
                    });
552
                }
553
            }
554
        });
555

UNCOV
556
        this.filteringDone.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
557
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
558
                const leafRowsDirectParents = new Set<any>();
×
UNCOV
559
                this.records.forEach(record => {
×
UNCOV
560
                    if (record && (!record.children || record.children.length === 0) && record.parent) {
×
UNCOV
561
                        leafRowsDirectParents.add(record.parent);
×
562
                    }
563
                });
UNCOV
564
                this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(leafRowsDirectParents);
×
UNCOV
565
                this.notifyChanges();
×
566
            }
567
        });
568
    }
569

570
    /**
571
     * @hidden
572
     */
573
    public override ngAfterViewInit() {
UNCOV
574
        super.ngAfterViewInit();
×
575
        // TODO: pipesExectured event
576
        // run after change detection in super triggers pipes for records structure
UNCOV
577
        if (this.rowSelection === GridSelectionMode.multipleCascade && this.selectedRows.length) {
×
578
            const selRows = this.selectedRows;
×
579
            this.selectionService.clearRowSelection();
×
580
            this.selectRows(selRows, true);
×
581
            this.cdr.detectChanges();
×
582
        }
583
    }
584

585
    /**
586
     * @hidden
587
     */
588
    public override ngAfterContentInit() {
UNCOV
589
        if (this.rowLoadingTemplate) {
×
590
            this._rowLoadingIndicatorTemplate = this.rowLoadingTemplate.template;
×
591
        }
UNCOV
592
        super.ngAfterContentInit();
×
593
    }
594

595
    public override getDefaultExpandState(record: ITreeGridRecord) {
596
        return record.children && record.children.length && record.level < this.expansionDepth;
×
597
    }
598

599
    /**
600
     * Expands all rows.
601
     * ```typescript
602
     * this.grid.expandAll();
603
     * ```
604
     *
605
     * @memberof IgxTreeGridComponent
606
     */
607
    public override expandAll() {
UNCOV
608
        this._expansionDepth = Infinity;
×
UNCOV
609
        this.expansionStates = new Map<any, boolean>();
×
610
    }
611

612
    /**
613
     * Collapses all rows.
614
     *
615
     * ```typescript
616
     * this.grid.collapseAll();
617
     *  ```
618
     *
619
     * @memberof IgxTreeGridComponent
620
     */
621
    public override collapseAll() {
UNCOV
622
        this._expansionDepth = 0;
×
UNCOV
623
        this.expansionStates = new Map<any, boolean>();
×
624
    }
625

626
    /**
627
     * @hidden
628
     */
629
    public override refreshGridState(args?: IRowDataEventArgs) {
UNCOV
630
        super.refreshGridState();
×
UNCOV
631
        if (this.primaryKey && this.foreignKey && args) {
×
UNCOV
632
            const rowID = args.data[this.foreignKey];
×
UNCOV
633
            this.summaryService.clearSummaryCache({ rowID });
×
UNCOV
634
            this.pipeTrigger++;
×
UNCOV
635
            this.cdr.detectChanges();
×
636
        }
637
    }
638

639
    /* blazorCSSuppress */
640
    /**
641
     * Creates a new `IgxTreeGridRowComponent` with the given data. If a parentRowID is not specified, the newly created
642
     * row would be added at the root level. Otherwise, it would be added as a child of the row whose primaryKey matches
643
     * the specified parentRowID. If the parentRowID does not exist, an error would be thrown.
644
     * ```typescript
645
     * const record = {
646
     *     ID: this.grid.data[this.grid1.data.length - 1].ID + 1,
647
     *     Name: this.newRecord
648
     * };
649
     * this.grid.addRow(record, 1); // Adds a new child row to the row with ID=1.
650
     * ```
651
     *
652
     * @param data
653
     * @param parentRowID
654
     * @memberof IgxTreeGridComponent
655
     */
656
    // TODO: remove evt emission
657
    public override addRow(data: any, parentRowID?: any) {
UNCOV
658
        this.crudService.endEdit(true);
×
UNCOV
659
        this.gridAPI.addRowToData(data, parentRowID);
×
660

UNCOV
661
        this.rowAddedNotifier.next({
×
662
            data: data,
663
            rowData: data, owner: this,
664
            primaryKey: data[this.primaryKey],
665
            rowKey: data[this.primaryKey]
666
        });
UNCOV
667
        this.pipeTrigger++;
×
UNCOV
668
        this.notifyChanges();
×
669
    }
670

671
    /**
672
     * Enters add mode by spawning the UI with the context of the specified row by index.
673
     *
674
     * @remarks
675
     * Accepted values for index are integers from 0 to this.grid.dataView.length
676
     * @remarks
677
     * When adding the row as a child, the parent row is the specified row.
678
     * @remarks
679
     * To spawn the UI on top, call the function with index = null or a negative number.
680
     * In this case trying to add this row as a child will result in error.
681
     * @example
682
     * ```typescript
683
     * this.grid.beginAddRowByIndex(10);
684
     * this.grid.beginAddRowByIndex(10, true);
685
     * this.grid.beginAddRowByIndex(null);
686
     * ```
687
     * @param index - The index to spawn the UI at. Accepts integers from 0 to this.grid.dataView.length
688
     * @param asChild - Whether the record should be added as a child. Only applicable to igxTreeGrid.
689
     */
690
    public override beginAddRowByIndex(index: number, asChild?: boolean): void {
691
        if (index === null || index < 0) {
×
692
            return this.beginAddRowById(null, asChild);
×
693
        }
694
        return this._addRowForIndex(index - 1, asChild);
×
695
    }
696

697
    /**
698
     * @hidden
699
     */
700
    public getContext(rowData: any, rowIndex: number, pinned?: boolean): any {
UNCOV
701
        return {
×
702
            $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData,
×
703
            index: this.getDataViewIndex(rowIndex, pinned),
704
            templateID: {
705
                type: this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow',
×
706
                id: null
707
            },
708
            disabled: this.isGhostRecord(rowData) ? rowData.recordRef.isFilteredOutParent === undefined : false
×
709
        };
710
    }
711

712
    /**
713
     * @hidden
714
     * @internal
715
     */
716
    public override getInitialPinnedIndex(rec) {
UNCOV
717
        const id = this.gridAPI.get_row_id(rec);
×
UNCOV
718
        return this._pinnedRecordIDs.indexOf(id);
×
719
    }
720

721
    /**
722
     * @hidden
723
     * @internal
724
     */
725
    public override isRecordPinned(rec) {
UNCOV
726
        return this.getInitialPinnedIndex(rec.data) !== -1;
×
727
    }
728

729
    /**
730
     *
731
     * Returns an array of the current cell selection in the form of `[{ column.field: cell.value }, ...]`.
732
     *
733
     * @remarks
734
     * If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
735
     * If `headers` is enabled, it will use the column header (if any) instead of the column field.
736
     */
737
    public override getSelectedData(formatters = false, headers = false): any[] {
×
UNCOV
738
        let source = [];
×
739

UNCOV
740
        const process = (record) => {
×
UNCOV
741
            if (record.summaries) {
×
UNCOV
742
                source.push(null);
×
UNCOV
743
                return;
×
744
            }
UNCOV
745
            source.push(record.data);
×
746
        };
747

UNCOV
748
        this.unpinnedDataView.forEach(process);
×
UNCOV
749
        source = this.isRowPinningToTop ? [...this.pinnedDataView, ...source] : [...source, ...this.pinnedDataView];
×
UNCOV
750
        return this.extractDataFromSelection(source, formatters, headers);
×
751
    }
752

753
    /**
754
     * @hidden @internal
755
     */
756
    public override getEmptyRecordObjectFor(inTreeRow: RowType) {
UNCOV
757
        const treeRowRec = inTreeRow?.treeRow || null;
×
UNCOV
758
        const row = { ...treeRowRec };
×
UNCOV
759
        const data = treeRowRec?.data || {};
×
UNCOV
760
        row.data = { ...data };
×
UNCOV
761
        Object.keys(row.data).forEach(key => {
×
762
            // persist foreign key if one is set.
UNCOV
763
            if (this.foreignKey && key === this.foreignKey) {
×
UNCOV
764
                row.data[key] = treeRowRec.data[this.crudService.addRowParent?.asChild ? this.primaryKey : key];
×
765
            } else {
UNCOV
766
                row.data[key] = undefined;
×
767
            }
768
        });
UNCOV
769
        let id = this.generateRowID();
×
UNCOV
770
        const rootRecPK = this.foreignKey && this.rootRecords && this.rootRecords.length > 0 ?
×
771
            this.rootRecords[0].data[this.foreignKey] : null;
UNCOV
772
        if (id === rootRecPK) {
×
773
            // safeguard in case generated id matches the root foreign key.
774
            id = this.generateRowID();
×
775
        }
UNCOV
776
        row.key = id;
×
UNCOV
777
        row.data[this.primaryKey] = id;
×
UNCOV
778
        return { rowID: id, data: row.data, recordRef: row };
×
779
    }
780

781
    /** @hidden */
782
    public override deleteRowById(rowId: any): any {
783
        //  if this is flat self-referencing data, and CascadeOnDelete is set to true
784
        //  and if we have transactions we should start pending transaction. This allows
785
        //  us in case of delete action to delete all child rows as single undo action
UNCOV
786
        const args: IRowDataCancelableEventArgs = {
×
787
            rowID: rowId,
788
            primaryKey: rowId,
789
            rowKey: rowId,
790
            cancel: false,
791
            rowData: this.getRowData(rowId),
792
            data: this.getRowData(rowId),
793
            oldValue: null,
794
            owner: this
795
        };
UNCOV
796
        this.rowDelete.emit(args);
×
UNCOV
797
        if (args.cancel) {
×
UNCOV
798
            return;
×
799
        }
800

UNCOV
801
        const record = this.gridAPI.deleteRowById(rowId);
×
UNCOV
802
        const key = record[this.primaryKey];
×
UNCOV
803
        if (record !== null && record !== undefined) {
×
UNCOV
804
            const rowDeletedEventArgs: IRowDataEventArgs = {
×
805
                data: record,
806
                rowData: record,
807
                owner: this,
808
                primaryKey: key,
809
                rowKey: key
810
            };
UNCOV
811
            this.rowDeleted.emit(rowDeletedEventArgs);
×
812
        }
UNCOV
813
        return record;
×
814
    }
815

816
    /**
817
     * Returns the `IgxTreeGridRow` by index.
818
     *
819
     * @example
820
     * ```typescript
821
     * const myRow = treeGrid.getRowByIndex(1);
822
     * ```
823
     * @param index
824
     */
825
    public getRowByIndex(index: number): RowType {
UNCOV
826
        if (index < 0 || index >= this.dataView.length) {
×
UNCOV
827
            return undefined;
×
828
        }
UNCOV
829
        return this.createRow(index);
×
830
    }
831

832
    /**
833
     * Returns the `RowType` object by the specified primary key.
834
     *
835
     * @example
836
     * ```typescript
837
     * const myRow = this.treeGrid.getRowByIndex(1);
838
     * ```
839
     * @param index
840
     */
841
    public getRowByKey(key: any): RowType {
UNCOV
842
        const rec = this.filteredSortedData ? this.primaryKey ? this.filteredSortedData.find(r => r[this.primaryKey] === key) :
×
UNCOV
843
            this.filteredSortedData.find(r => r === key) : undefined;
×
UNCOV
844
        const index = this.dataView.findIndex(r => r.data && r.data === rec);
×
UNCOV
845
        if (index < 0 || index >= this.filteredSortedData.length) {
×
UNCOV
846
            return undefined;
×
847
        }
UNCOV
848
        return new IgxTreeGridRow(this as any, index, rec);
×
849
    }
850

851
    /**
852
     * Returns the collection of all RowType for current page.
853
     *
854
     * @hidden @internal
855
     */
856
    public allRows(): RowType[] {
UNCOV
857
        return this.dataView.map((rec, index) => this.createRow(index));
×
858
    }
859

860
    /**
861
     * Returns the collection of `IgxTreeGridRow`s for current page.
862
     *
863
     * @hidden @internal
864
     */
865
    public dataRows(): RowType[] {
UNCOV
866
        return this.allRows().filter(row => row instanceof IgxTreeGridRow);
×
867
    }
868

869
    /**
870
     * Returns an array of the selected `IgxGridCell`s.
871
     *
872
     * @example
873
     * ```typescript
874
     * const selectedCells = this.grid.selectedCells;
875
     * ```
876
     */
877
    public get selectedCells(): CellType[] {
UNCOV
878
        return this.dataRows().map((row) => row.cells.filter((cell) => cell.selected))
×
UNCOV
879
            .reduce((a, b) => a.concat(b), []);
×
880
    }
881

882
    /**
883
     * Returns a `CellType` object that matches the conditions.
884
     *
885
     * @example
886
     * ```typescript
887
     * const myCell = this.grid1.getCellByColumn(2, "UnitPrice");
888
     * ```
889
     * @param rowIndex
890
     * @param columnField
891
     */
892
    public getCellByColumn(rowIndex: number, columnField: string): CellType {
UNCOV
893
        const row = this.getRowByIndex(rowIndex);
×
UNCOV
894
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
895
        if (row && row instanceof IgxTreeGridRow && column) {
×
UNCOV
896
            return new IgxGridCell(this as any, rowIndex, column);
×
897
        }
898
    }
899

900
    /**
901
     * Returns a `CellType` object that matches the conditions.
902
     *
903
     * @remarks
904
     * Requires that the primaryKey property is set.
905
     * @example
906
     * ```typescript
907
     * grid.getCellByKey(1, 'index');
908
     * ```
909
     * @param rowSelector match any rowID
910
     * @param columnField
911
     */
912
    public getCellByKey(rowSelector: any, columnField: string): CellType {
UNCOV
913
        const row = this.getRowByKey(rowSelector);
×
UNCOV
914
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
915
        if (row && column) {
×
UNCOV
916
            return new IgxGridCell(this as any, row.index, column);
×
917
        }
918
    }
919

920
    public override pinRow(rowID: any, index?: number): boolean {
UNCOV
921
        const row = this.getRowByKey(rowID);
×
UNCOV
922
        return super.pinRow(rowID, index, row);
×
923
    }
924

925
    public override unpinRow(rowID: any): boolean {
UNCOV
926
        const row = this.getRowByKey(rowID);
×
UNCOV
927
        return super.unpinRow(rowID, row);
×
928
    }
929

930
    /** @hidden */
931
    public generateRowPath(rowId: any): any[] {
UNCOV
932
        const path: any[] = [];
×
UNCOV
933
        let record = this.records.get(rowId);
×
934

UNCOV
935
        while (record.parent) {
×
UNCOV
936
            path.push(record.parent.key);
×
UNCOV
937
            record = record.parent;
×
938
        }
939

UNCOV
940
        return path.reverse();
×
941
    }
942

943
    /** @hidden */
944
    public isTreeRow(record: any): boolean {
UNCOV
945
        return record.key !== undefined && record.data;
×
946
    }
947

948
    /** @hidden */
949
    public override getUnpinnedIndexById(id) {
UNCOV
950
        return this.unpinnedRecords.findIndex(x => x.data[this.primaryKey] === id);
×
951
    }
952

953
    /**
954
     * @hidden
955
     */
956
    public createRow(index: number, data?: any): RowType {
957
        let row: RowType;
UNCOV
958
        const dataIndex = this._getDataViewIndex(index);
×
UNCOV
959
        const rec: any = data ?? this.dataView[dataIndex];
×
960

UNCOV
961
        if (this.isSummaryRow(rec)) {
×
UNCOV
962
            row = new IgxSummaryRow(this as any, index, rec.summaries);
×
963
        }
964

UNCOV
965
        if (!row && rec) {
×
UNCOV
966
            const isTreeRow = this.isTreeRow(rec);
×
UNCOV
967
            const dataRec = isTreeRow ? rec.data : rec;
×
UNCOV
968
            const treeRow = isTreeRow ? rec : undefined;
×
UNCOV
969
            row = new IgxTreeGridRow(this as any, index, dataRec, treeRow);
×
970
        }
971

UNCOV
972
        return row;
×
973
    }
974

975
    protected override generateDataFields(data: any[]): string[] {
UNCOV
976
        return super.generateDataFields(data).filter(field => field !== this.childDataKey);
×
977
    }
978

979
    protected override transactionStatusUpdate(event: StateUpdateEvent) {
UNCOV
980
        let actions = [];
×
UNCOV
981
        if (event.origin === TransactionEventOrigin.REDO) {
×
UNCOV
982
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.DELETE) : [];
×
UNCOV
983
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
984
                this.handleCascadeSelection(event);
×
985
            }
UNCOV
986
        } else if (event.origin === TransactionEventOrigin.UNDO) {
×
UNCOV
987
            actions = event.actions ? event.actions.filter(x => x.transaction.type === TransactionType.ADD) : [];
×
UNCOV
988
            if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
UNCOV
989
                if (event.actions[0].transaction.type === 'add') {
×
UNCOV
990
                    const rec = this.gridAPI.get_rec_by_id(event.actions[0].transaction.id);
×
UNCOV
991
                    this.handleCascadeSelection(event, rec);
×
992
                } else {
UNCOV
993
                    this.handleCascadeSelection(event);
×
994
                }
995
            }
996
        }
UNCOV
997
        if (actions.length) {
×
UNCOV
998
            for (const action of actions) {
×
UNCOV
999
                this.deselectChildren(action.transaction.id);
×
1000
            }
1001
        }
UNCOV
1002
        super.transactionStatusUpdate(event);
×
1003
    }
1004

1005
    protected findRecordIndexInView(rec) {
1006
        return this.dataView.findIndex(x => x.data[this.primaryKey] === rec[this.primaryKey]);
×
1007
    }
1008

1009
    /**
1010
     * @hidden @internal
1011
     */
1012
    protected override getDataBasedBodyHeight(): number {
UNCOV
1013
        return !this.flatData || (this.flatData.length < this._defaultTargetRecordNumber) ?
×
1014
            0 : this.defaultTargetBodyHeight;
1015
    }
1016

1017
    /**
1018
     * @hidden
1019
     */
1020
    protected override scrollTo(row: any | number, column: any | number): void {
UNCOV
1021
        let delayScrolling = false;
×
1022
        let record: ITreeGridRecord;
1023

UNCOV
1024
        if (typeof (row) !== 'number') {
×
UNCOV
1025
            const rowData = row;
×
UNCOV
1026
            const rowID = this.gridAPI.get_row_id(rowData);
×
UNCOV
1027
            record = this.processedRecords.get(rowID);
×
UNCOV
1028
            this.gridAPI.expand_path_to_record(record);
×
1029

UNCOV
1030
            if (this.paginator) {
×
UNCOV
1031
                const rowIndex = this.processedExpandedFlatData.indexOf(rowData);
×
UNCOV
1032
                const page = Math.floor(rowIndex / this.perPage);
×
1033

UNCOV
1034
                if (this.page !== page) {
×
UNCOV
1035
                    delayScrolling = true;
×
UNCOV
1036
                    this.page = page;
×
1037
                }
1038
            }
1039
        }
1040

UNCOV
1041
        if (delayScrolling) {
×
UNCOV
1042
            this.verticalScrollContainer.dataChanged.pipe(first()).subscribe(() => {
×
UNCOV
1043
                this.scrollDirective(this.verticalScrollContainer,
×
1044
                    typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(record));
×
1045
            });
1046
        } else {
UNCOV
1047
            this.scrollDirective(this.verticalScrollContainer,
×
1048
                typeof (row) === 'number' ? row : this.unpinnedDataView.indexOf(record));
×
1049
        }
1050

UNCOV
1051
        this.scrollToHorizontally(column);
×
1052
    }
1053

1054
    protected override writeToData(rowIndex: number, value: any) {
1055
        mergeObjects(this.flatData[rowIndex], value);
×
1056
    }
1057

1058
    /**
1059
     * @hidden
1060
     */
1061
    protected override initColumns(collection: IgxColumnComponent[], cb: (args: any) => void = null) {
×
UNCOV
1062
        if (this.hasColumnLayouts) {
×
1063
            // invalid configuration - tree grid should not allow column layouts
1064
            // remove column layouts
1065
            const nonColumnLayoutColumns = this.columns.filter((col) => !col.columnLayout && !col.columnLayoutChild);
×
1066
            this.updateColumns(nonColumnLayoutColumns);
×
1067
        }
UNCOV
1068
        super.initColumns(collection, cb);
×
1069
    }
1070

1071
    /**
1072
     * @hidden @internal
1073
     */
1074
    protected override getGroupAreaHeight(): number {
UNCOV
1075
        return this.treeGroupArea ? this.getComputedHeight(this.treeGroupArea.nativeElement) : 0;
×
1076
    }
1077

1078
    /** {@link triggerPipes} will re-create pinnedData on CRUD operations */
1079
    protected trackPinnedRowData(record: ITreeGridRecord) {
1080
        // TODO FIX: pipeline data doesn't match end interface (¬_¬ )
1081
        // return record.key || (record as any).rowID;
UNCOV
1082
        return record;
×
1083
    }
1084

1085
    /**
1086
     * @description A recursive way to deselect all selected children of a given record
1087
     * @param recordID ID of the record whose children to deselect
1088
     * @hidden
1089
     * @internal
1090
     */
1091
    private deselectChildren(recordID): void {
UNCOV
1092
        const selectedChildren = [];
×
1093
        // G.E. Apr 28, 2021 #9465 Records which are not in view can also be selected so we need to
1094
        // deselect them as well, hence using 'records' map instead of getRowByKey() method which will
1095
        // return only row components (i.e. records in view).
UNCOV
1096
        const rowToDeselect = this.records.get(recordID);
×
UNCOV
1097
        this.selectionService.deselectRowsWithNoEvent([recordID]);
×
UNCOV
1098
        this.gridAPI.get_selected_children(rowToDeselect, selectedChildren);
×
UNCOV
1099
        if (selectedChildren.length > 0) {
×
UNCOV
1100
            selectedChildren.forEach(x => this.deselectChildren(x));
×
1101
        }
1102
    }
1103

1104
    private addChildRows(children: any[], parentID: any) {
UNCOV
1105
        if (this.primaryKey && this.foreignKey) {
×
UNCOV
1106
            for (const child of children) {
×
UNCOV
1107
                child[this.foreignKey] = parentID;
×
1108
            }
UNCOV
1109
            this.data.push(...children);
×
UNCOV
1110
        } else if (this.childDataKey) {
×
UNCOV
1111
            let parent = this.records.get(parentID);
×
UNCOV
1112
            let parentData = parent.data;
×
1113

UNCOV
1114
            if (this.transactions.enabled && this.transactions.getAggregatedChanges(true).length) {
×
1115
                const path = [];
×
1116
                while (parent) {
×
1117
                    path.push(parent.key);
×
1118
                    parent = parent.parent;
×
1119
                }
1120

1121
                let collection = this.data;
×
1122
                let record: any;
1123
                for (let i = path.length - 1; i >= 0; i--) {
×
1124
                    const pid = path[i];
×
1125
                    record = collection.find(r => r[this.primaryKey] === pid);
×
1126

1127
                    if (!record) {
×
1128
                        break;
×
1129
                    }
1130
                    collection = record[this.childDataKey];
×
1131
                }
1132
                if (record) {
×
1133
                    parentData = record;
×
1134
                }
1135
            }
1136

UNCOV
1137
            parentData[this.childDataKey] = children;
×
1138
        }
UNCOV
1139
        this.selectionService.clearHeaderCBState();
×
UNCOV
1140
        this.pipeTrigger++;
×
UNCOV
1141
        if (this.rowSelection === GridSelectionMode.multipleCascade) {
×
1142
            // Force pipe triggering for building the data structure
UNCOV
1143
            this.cdr.detectChanges();
×
UNCOV
1144
            if (this.selectionService.isRowSelected(parentID)) {
×
UNCOV
1145
                this.selectionService.rowSelection.delete(parentID);
×
UNCOV
1146
                this.selectionService.selectRowsWithNoEvent([parentID]);
×
1147
            }
1148
        }
1149
    }
1150

1151
    private loadChildrenOnRowExpansion(args: IRowToggleEventArgs) {
UNCOV
1152
        if (this.loadChildrenOnDemand) {
×
UNCOV
1153
            const parentID = args.rowID;
×
1154

UNCOV
1155
            if (args.expanded && !this._expansionStates.has(parentID)) {
×
UNCOV
1156
                this.loadingRows.add(parentID);
×
1157

UNCOV
1158
                this.loadChildrenOnDemand(parentID, children => {
×
UNCOV
1159
                    this.loadingRows.delete(parentID);
×
UNCOV
1160
                    this.addChildRows(children, parentID);
×
UNCOV
1161
                    this.notifyChanges();
×
1162
                });
1163
            }
1164
        }
1165
    }
1166

1167
    private handleCascadeSelection(event: IRowDataEventArgs | StateUpdateEvent, rec: ITreeGridRecord = null) {
×
1168
        // Wait for the change detection to update records through the pipes
UNCOV
1169
        requestAnimationFrame(() => {
×
UNCOV
1170
            if (rec === null) {
×
UNCOV
1171
                rec = this.gridAPI.get_rec_by_id((event as StateUpdateEvent).actions[0].transaction.id);
×
1172
            }
UNCOV
1173
            if (rec && rec.parent) {
×
UNCOV
1174
                this.gridAPI.grid.selectionService.updateCascadeSelectionOnFilterAndCRUD(
×
1175
                    new Set([rec.parent]), rec.parent.key
1176
                );
UNCOV
1177
                this.notifyChanges();
×
1178
            }
1179
        });
1180
    }
1181
}
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