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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

34.97
/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts
1
import {
2
    AfterContentInit,
3
    AfterViewInit,
4
    booleanAttribute,
5
    ChangeDetectionStrategy,
6
    ChangeDetectorRef,
7
    Component,
8
    ContentChildren,
9
    CUSTOM_ELEMENTS_SCHEMA,
10
    DoCheck,
11
    ElementRef,
12
    HostBinding,
13
    Inject,
14
    Input,
15
    OnDestroy,
16
    OnInit,
17
    QueryList,
18
    reflectComponentType,
19
    SimpleChanges,
20
    TemplateRef,
21
    ViewChild,
22
    ViewChildren,
23
    ViewContainerRef
24
} from '@angular/core';
25
import { NgIf, NgClass, NgFor, NgTemplateOutlet, NgStyle } from '@angular/common';
26

27
import { IgxHierarchicalGridAPIService } from './hierarchical-grid-api.service';
28
import { IgxRowIslandComponent } from './row-island.component';
29
import { IgxFilteringService } from '../filtering/grid-filtering.service';
30
import { IgxColumnComponent, } from '../columns/column.component';
31
import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service';
32
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
33
import { IgxHierarchicalGridBaseDirective } from './hierarchical-grid-base.directive';
34
import { takeUntil } from 'rxjs/operators';
35
import { IgxTemplateOutletDirective } from '../../directives/template-outlet/template_outlet.directive';
36
import { IgxGridSelectionService } from '../selection/selection.service';
37
import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives/for-of/for_of.sync.service';
38
import { CellType, GridType, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface';
39
import { IgxRowIslandAPIService } from './row-island-api.service';
40
import { IgxGridCRUDService } from '../common/crud.service';
41
import { IgxHierarchicalGridRow } from '../grid-public-row';
42
import { IgxGridCell } from '../grid-public-cell';
43
import type { IgxPaginatorComponent } from '../../paginator/paginator.component';
44
import { IgxPaginatorToken } from '../../paginator/token';
45
import { IgxGridComponent } from '../grid/grid.component';
46
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../../directives/toggle/toggle.directive';
47
import { IgxColumnResizingService } from '../resizing/resizing.service';
48
import { IgxGridExcelStyleFilteringComponent } from '../filtering/excel-style/excel-style-filtering.component';
49
import { IgxGridValidationService } from '../grid/grid-validation.service';
50
import { IgxGridHierarchicalPipe, IgxGridHierarchicalPagingPipe } from './hierarchical-grid.pipes';
51
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
52
import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
53
import { IgxGridSortingPipe, IgxGridFilteringPipe } from '../grid/grid.pipes';
54
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
55
import { IgxRowEditTabStopDirective } from '../grid.rowEdit.directive';
56
import { IgxIconComponent } from '../../icon/icon.component';
57
import { IgxRippleDirective } from '../../directives/ripple/ripple.directive';
58
import { IgxButtonDirective } from '../../directives/button/button.directive';
59
import { IgxSummaryRowComponent } from '../summaries/summary-row.component';
60
import { IgxSnackbarComponent } from '../../snackbar/snackbar.component';
61
import { IgxCircularProgressBarComponent } from '../../progressbar/progressbar.component';
62
import { IgxHierarchicalRowComponent } from './hierarchical-row.component';
63
import { IgxGridForOfDirective } from '../../directives/for-of/for_of.directive';
64
import { IgxColumnMovingDropDirective } from '../moving/moving.drop.directive';
65
import { IgxGridDragSelectDirective } from '../selection/drag-select.directive';
66
import { IgxGridBodyDirective } from '../grid.common';
67
import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component';
68
import { IgxActionStripToken } from '../../action-strip/token';
69

70
let NEXT_ID = 0;
2✔
71

72
/**
73
 * @hidden @internal
74
 */
75
@Component({
76
    changeDetection: ChangeDetectionStrategy.OnPush,
77
    selector: 'igx-child-grid-row',
78
    templateUrl: './child-grid-row.component.html',
79
    imports: [NgClass]
80
})
81
export class IgxChildGridRowComponent implements AfterViewInit, OnInit {
2✔
82
    @Input()
83
    public layout: IgxRowIslandComponent;
84

85
    /**
86
     * @hidden
87
     */
88
    public get parentHasScroll() {
UNCOV
89
        return !this.parentGrid.verticalScrollContainer.dc.instance.notVirtual;
×
90
    }
91

92

93
    /**
94
     * @hidden
95
     */
96
    @Input()
97
    public parentGridID: string;
98

99
    /**
100
     *  The data passed to the row component.
101
     *
102
     * ```typescript
103
     * // get the row data for the first selected row
104
     * let selectedRowData = this.grid.selectedRows[0].data;
105
     * ```
106
     */
107
    @Input()
108
    public get data(): any {
UNCOV
109
        return this._data || [];
×
110
    }
111

112
    public set data(value: any) {
UNCOV
113
        this._data = value;
×
UNCOV
114
        if (this.hGrid && !this.hGrid.dataSetByUser) {
×
UNCOV
115
            this.hGrid.setDataInternal(this._data.childGridsData[this.layout.key]);
×
116
        }
117
    }
118

119
    /**
120
     * The index of the row.
121
     *
122
     * ```typescript
123
     * // get the index of the second selected row
124
     * let selectedRowIndex = this.grid.selectedRows[1].index;
125
     * ```
126
     */
127
    @Input()
128
    public index: number;
129

130
    /* blazorSuppress */
131
    @ViewChild('container', { read: ViewContainerRef, static: true })
132
    public container: ViewContainerRef;
133

134
    /**
135
     * @hidden
136
     */
137
    public hGrid: IgxHierarchicalGridComponent;
138

139
    /* blazorSuppress */
140
    /**
141
     * Get a reference to the grid that contains the selected row.
142
     *
143
     * ```typescript
144
     * handleRowSelection(event) {
145
     *  // the grid on which the rowSelected event was triggered
146
     *  const grid = event.row.grid;
147
     * }
148
     * ```
149
     *
150
     * ```html
151
     *  <igx-grid
152
     *    [data]="data"
153
     *    (rowSelected)="handleRowSelection($event)">
154
     *  </igx-grid>
155
     * ```
156
     */
157
    // TODO: Refactor
158
    public get parentGrid(): IgxHierarchicalGridComponent {
UNCOV
159
        return this.gridAPI.grid as IgxHierarchicalGridComponent;
×
160
    }
161

162
    @HostBinding('attr.aria-level')
163
    public get level() {
UNCOV
164
        return this.layout.level;
×
165
    }
166

167
    /**
168
     * The native DOM element representing the row. Could be null in certain environments.
169
     *
170
     * ```typescript
171
     * // get the nativeElement of the second selected row
172
     * let selectedRowNativeElement = this.grid.selectedRows[1].nativeElement;
173
     * ```
174
     */
175
    public get nativeElement() {
UNCOV
176
        return this.element.nativeElement;
×
177
    }
178

179
    /**
180
     * Returns whether the row is expanded.
181
     * ```typescript
182
     * const RowExpanded = this.grid1.rowList.first.expanded;
183
     * ```
184
     */
UNCOV
185
    public expanded = false;
×
186

187
    private _data: any;
188

189
    constructor(
UNCOV
190
        @Inject(IGX_GRID_SERVICE_BASE) public readonly gridAPI: IgxHierarchicalGridAPIService,
×
UNCOV
191
        public element: ElementRef<HTMLElement>,
×
UNCOV
192
        public cdr: ChangeDetectorRef) { }
×
193

194
    /**
195
     * @hidden
196
     */
197
    public ngOnInit() {
UNCOV
198
        const ref = this.container.createComponent(IgxHierarchicalGridComponent, { injector: this.container.injector });
×
UNCOV
199
        this.hGrid = ref.instance;
×
UNCOV
200
        this.hGrid.setDataInternal(this.data.childGridsData[this.layout.key]);
×
UNCOV
201
        this.hGrid.nativeElement["__componentRef"] = ref;
×
UNCOV
202
        this.layout.layoutChange.subscribe((ch) => {
×
UNCOV
203
            this._handleLayoutChanges(ch);
×
204
        });
UNCOV
205
        const changes = this.layout.initialChanges;
×
UNCOV
206
        changes.forEach(change => {
×
UNCOV
207
            this._handleLayoutChanges(change);
×
208
        });
UNCOV
209
        this.hGrid.parent = this.parentGrid;
×
UNCOV
210
        this.hGrid.parentIsland = this.layout;
×
UNCOV
211
        this.hGrid.childRow = this;
×
212
        // handler logic that re-emits hgrid events on the row island
UNCOV
213
        this.setupEventEmitters();
×
UNCOV
214
        this.layout.gridCreated.emit({
×
215
            owner: this.layout,
216
            parentID: this.data.rowID,
217
            grid: this.hGrid
218
        });
219
    }
220

221
    /**
222
     * @hidden
223
     */
224
    public ngAfterViewInit() {
UNCOV
225
        this.hGrid.childLayoutList = this.layout.children;
×
UNCOV
226
        const layouts = this.hGrid.childLayoutList.toArray();
×
UNCOV
227
        layouts.forEach((l) => this.hGrid.gridAPI.registerChildRowIsland(l));
×
UNCOV
228
        this.parentGrid.gridAPI.registerChildGrid(this.data.rowID, this.layout.key, this.hGrid);
×
UNCOV
229
        this.layout.rowIslandAPI.registerChildGrid(this.data.rowID, this.hGrid);
×
230

UNCOV
231
        this.layout.gridInitialized.emit({
×
232
            owner: this.layout,
233
            parentID: this.data.rowID,
234
            grid: this.hGrid
235
        });
236

UNCOV
237
        this.hGrid.cdr.detectChanges();
×
238
    }
239

240
    private setupEventEmitters() {
UNCOV
241
        const destructor = takeUntil(this.hGrid.destroy$);
×
242

UNCOV
243
        const mirror = reflectComponentType(IgxGridComponent);
×
244
        // exclude outputs related to two-way binding functionality
UNCOV
245
        const inputNames = mirror.inputs.map(input => input.propName);
×
UNCOV
246
        const outputs = mirror.outputs.filter(o => {
×
UNCOV
247
            const matchingInputPropName = o.propName.slice(0, o.propName.indexOf('Change'));
×
UNCOV
248
            return inputNames.indexOf(matchingInputPropName) === -1;
×
249
        });
250

251
        // TODO: Skip the `rendered` output. Rendered should be called once per grid.
UNCOV
252
        outputs.filter(o => o.propName !== 'rendered').forEach(output => {
×
UNCOV
253
            if (this.hGrid[output.propName]) {
×
UNCOV
254
                this.hGrid[output.propName].pipe(destructor).subscribe((args) => {
×
UNCOV
255
                    if (!args) {
×
256
                        args = {};
×
257
                    }
UNCOV
258
                    args.owner = this.hGrid;
×
UNCOV
259
                    this.layout[output.propName].emit(args);
×
260
                });
261
            }
262
        });
263
    }
264

265

266
    private _handleLayoutChanges(changes: SimpleChanges) {
UNCOV
267
        for (const change in changes) {
×
UNCOV
268
            if (changes.hasOwnProperty(change)) {
×
UNCOV
269
                this.hGrid[change] = changes[change].currentValue;
×
270
            }
271
        }
272
    }
273
}
274

275

276
/* blazorAdditionalDependency: Column */
277
/* blazorAdditionalDependency: ColumnGroup */
278
/* blazorAdditionalDependency: ColumnLayout */
279
/* blazorAdditionalDependency: GridToolbar */
280
/* blazorAdditionalDependency: GridToolbarActions */
281
/* blazorAdditionalDependency: GridToolbarTitle */
282
/* blazorAdditionalDependency: GridToolbarAdvancedFiltering */
283
/* blazorAdditionalDependency: GridToolbarExporter */
284
/* blazorAdditionalDependency: GridToolbarHiding */
285
/* blazorAdditionalDependency: GridToolbarPinning */
286
/* blazorAdditionalDependency: ActionStrip */
287
/* blazorAdditionalDependency: GridActionsBaseDirective */
288
/* blazorAdditionalDependency: GridEditingActions */
289
/* blazorAdditionalDependency: GridPinningActions */
290
/* blazorAdditionalDependency: RowIsland */
291
/* blazorIndirectRender */
292
/**
293
 * Hierarchical grid
294
 *
295
 * @igxModule IgxHierarchicalGridModule
296
 *
297
 */
298
@Component({
299
    changeDetection: ChangeDetectionStrategy.OnPush,
300
    selector: 'igx-hierarchical-grid',
301
    templateUrl: 'hierarchical-grid.component.html',
302
    providers: [
303
        IgxGridCRUDService,
304
        IgxGridValidationService,
305
        IgxGridSelectionService,
306
        { provide: IGX_GRID_SERVICE_BASE, useClass: IgxHierarchicalGridAPIService },
307
        { provide: IGX_GRID_BASE, useExisting: IgxHierarchicalGridComponent },
308
        IgxGridSummaryService,
309
        IgxFilteringService,
310
        IgxHierarchicalGridNavigationService,
311
        IgxColumnResizingService,
312
        IgxForOfSyncService,
313
        IgxForOfScrollSyncService,
314
        IgxRowIslandAPIService
315
    ],
316
    imports: [
317
        NgIf,
318
        NgClass,
319
        NgFor,
320
        NgTemplateOutlet,
321
        NgStyle,
322
        IgxGridHeaderRowComponent,
323
        IgxGridBodyDirective,
324
        IgxGridDragSelectDirective,
325
        IgxColumnMovingDropDirective,
326
        IgxGridForOfDirective,
327
        IgxTemplateOutletDirective,
328
        IgxHierarchicalRowComponent,
329
        IgxOverlayOutletDirective,
330
        IgxToggleDirective,
331
        IgxCircularProgressBarComponent,
332
        IgxSnackbarComponent,
333
        IgxSummaryRowComponent,
334
        IgxButtonDirective,
335
        IgxRippleDirective,
336
        IgxIconComponent,
337
        IgxRowEditTabStopDirective,
338
        IgxGridColumnResizerComponent,
339
        IgxChildGridRowComponent,
340
        IgxGridSortingPipe,
341
        IgxGridFilteringPipe,
342
        IgxGridTransactionPipe,
343
        IgxHasVisibleColumnsPipe,
344
        IgxGridRowPinningPipe,
345
        IgxGridAddRowPipe,
346
        IgxGridRowClassesPipe,
347
        IgxGridRowStylesPipe,
348
        IgxSummaryDataPipe,
349
        IgxGridHierarchicalPipe,
350
        IgxGridHierarchicalPagingPipe,
351
        IgxStringReplacePipe
352
    ],
353
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
354
})
355
export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirective
2✔
356
    implements GridType, AfterViewInit, AfterContentInit, OnInit, OnDestroy, DoCheck {
357

358
    /**
359
     * @hidden @internal
360
     */
361
    @HostBinding('attr.role')
362
    public role = 'grid';
1✔
363

364
    /* contentChildren */
365
    /* blazorInclude */
366
    /* blazorTreatAsCollection */
367
    /* blazorCollectionName: RowIslandCollection */
368
    /* ngQueryListName: childLayoutList */
369
    /**
370
     * @hidden
371
     */
372
    @ContentChildren(IgxRowIslandComponent, { read: IgxRowIslandComponent, descendants: false })
373
    public childLayoutList: QueryList<IgxRowIslandComponent>;
374

375
    /**
376
     * @hidden
377
     */
378
    @ContentChildren(IgxRowIslandComponent, { read: IgxRowIslandComponent, descendants: true })
379
    public allLayoutList: QueryList<IgxRowIslandComponent>;
380

381
    /** @hidden @internal */
382
    @ContentChildren(IgxPaginatorToken, { descendants: true })
383
    public paginatorList: QueryList<IgxPaginatorComponent>;
384

385
    /** @hidden @internal */
386
    @ViewChild('toolbarOutlet', { read: ViewContainerRef })
387
    public toolbarOutlet: ViewContainerRef;
388

389
    /** @hidden @internal */
390
    @ViewChild('paginatorOutlet', { read: ViewContainerRef })
391
    public paginatorOutlet: ViewContainerRef;
392
    /**
393
     * @hidden
394
     */
395
    @ViewChildren(IgxTemplateOutletDirective, { read: IgxTemplateOutletDirective })
396
    public templateOutlets: QueryList<any>;
397

398
    /**
399
     * @hidden
400
     */
401
    @ViewChildren(IgxChildGridRowComponent)
402
    public hierarchicalRows: QueryList<IgxChildGridRowComponent>;
403

404
    @ViewChild('hierarchical_record_template', { read: TemplateRef, static: true })
405
    protected hierarchicalRecordTemplate: TemplateRef<any>;
406

407
    @ViewChild('child_record_template', { read: TemplateRef, static: true })
408
    protected childTemplate: TemplateRef<any>;
409

410
    // @ViewChild('headerHierarchyExpander', { read: ElementRef, static: true })
411
    protected get headerHierarchyExpander() {
412
        return this.theadRow?.headerHierarchyExpander;
25✔
413
    }
414

415
    /**
416
     * @hidden
417
     */
418
    public childLayoutKeys = [];
1✔
419

420
    /** @hidden @internal */
421
    public dataSetByUser = false;
1✔
422

423
    /**
424
     * @hidden
425
     */
426
    public highlightedRowID = null;
1✔
427

428
    /**
429
     * @hidden
430
     */
431
    public updateOnRender = false;
1✔
432

433
    /**
434
     * @hidden
435
     */
436
    public parent: IgxHierarchicalGridComponent = null;
1✔
437

438
    /**
439
     * @hidden @internal
440
     */
441
    public childRow: IgxChildGridRowComponent;
442

443
    @ContentChildren(IgxActionStripToken, { read: IgxActionStripToken, descendants: false })
444
    protected override actionStripComponents: QueryList<IgxActionStripToken>;
445

446
    /** @hidden @internal */
447
    public override get actionStrip() {
448
        return this.parentIsland ? this.parentIsland.actionStrip : super.actionStrip;
1!
449
    }
450

451
    private _data;
452
    private h_id = `igx-hierarchical-grid-${NEXT_ID++}`;
1✔
453
    private childGridTemplates: Map<any, any> = new Map();
1✔
454

455
    /**
456
     * Gets/Sets the value of the `id` attribute.
457
     *
458
     * @remarks
459
     * If not provided it will be automatically generated.
460
     * @example
461
     * ```html
462
     * <igx-hierarchical-grid [id]="'igx-hgrid-1'" [data]="Data" [autoGenerate]="true"></igx-hierarchical-grid>
463
     * ```
464
     */
465
    @HostBinding('attr.id')
466
    @Input()
467
    public get id(): string {
468
        return this.h_id;
941✔
469
    }
470
    public set id(value: string) {
471
        this.h_id = value;
×
472
    }
473

474
    /* treatAsRef */
475
    /**
476
     * Gets/Sets the array of data that populates the component.
477
     * ```html
478
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true"></igx-hierarchical-grid>
479
     * ```
480
     *
481
     * @memberof IgxHierarchicalGridComponent
482
     */
483
    @Input()
484
    public set data(value: any[] | null) {
485
        this.setDataInternal(value);
1✔
486
        this.dataSetByUser = true;
1✔
487
        this.checkPrimaryKeyField();
1✔
488
    }
489

490
    /**
491
     * Returns an array of data set to the `IgxHierarchicalGridComponent`.
492
     * ```typescript
493
     * let filteredData = this.grid.filteredData;
494
     * ```
495
     *
496
     * @memberof IgxHierarchicalGridComponent
497
     */
498
    public get data(): any[] | null {
499
        return this._data;
54✔
500
    }
501

502
    /** @hidden @internal */
503
    public override get paginator() {
504
        const id = this.id;
689✔
505
        return (!this.parentIsland && this.paginationComponents?.first) || this.rootGrid.paginatorList?.find((pg) =>
689✔
UNCOV
506
            pg.nativeElement.offsetParent?.id === id);
×
507
    }
508

509
    /** @hidden @internal */
510
    public override get excelStyleFilteringComponent(): IgxGridExcelStyleFilteringComponent {
UNCOV
511
        return this.parentIsland ?
×
512
            this.parentIsland.excelStyleFilteringComponents.first :
513
            super.excelStyleFilteringComponent;
514
    }
515

516
    /**
517
     * Gets/Sets the total number of records in the data source.
518
     *
519
     * @remarks
520
     * This property is required for remote grid virtualization to function when it is bound to remote data.
521
     * @example
522
     * ```typescript
523
     * const itemCount = this.grid1.totalItemCount;
524
     * this.grid1.totalItemCount = 55;
525
     * ```
526
     */
527
    @Input()
528
    public set totalItemCount(count) {
529
        this.verticalScrollContainer.totalItemCount = count;
×
530
    }
531

532
    public get totalItemCount() {
UNCOV
533
        return this.verticalScrollContainer.totalItemCount;
×
534
    }
535

536
    /**
537
     * Sets if all immediate children of the `IgxHierarchicalGridComponent` should be expanded/collapsed.
538
     * Default value is false.
539
     * ```html
540
     * <igx-hierarchical-grid [id]="'igx-grid-1'" [data]="Data" [autoGenerate]="true" [expandChildren]="true"></igx-hierarchical-grid>
541
     * ```
542
     *
543
     * @memberof IgxHierarchicalGridComponent
544
     */
545
    @Input({ transform: booleanAttribute })
546
    public set expandChildren(value: boolean) {
UNCOV
547
        this._defaultExpandState = value;
×
UNCOV
548
        this.expansionStates = new Map<any, boolean>();
×
549
    }
550

551
    /**
552
     * Gets if all immediate children of the `IgxHierarchicalGridComponent` previously have been set to be expanded/collapsed.
553
     * If previously set and some rows have been manually expanded/collapsed it will still return the last set value.
554
     * ```typescript
555
     * const expanded = this.grid.expandChildren;
556
     * ```
557
     *
558
     * @memberof IgxHierarchicalGridComponent
559
     */
560
    public get expandChildren(): boolean {
561
        return this._defaultExpandState;
135✔
562
    }
563

564
    /**
565
     * Gets the unique identifier of the parent row. It may be a `string` or `number` if `primaryKey` of the
566
     * parent grid is set or an object reference of the parent record otherwise.
567
     * ```typescript
568
     * const foreignKey = this.grid.foreignKey;
569
     * ```
570
     *
571
     * @memberof IgxHierarchicalGridComponent
572
     */
573
    public get foreignKey() {
574
        if (!this.parent) {
×
575
            return null;
×
576
        }
577
        return this.parent.gridAPI.getParentRowId(this);
×
578
    }
579

580
    /**
581
     * @hidden
582
     */
583
    public get hasExpandableChildren() {
584
        return !!this.childLayoutKeys.length;
26✔
585
    }
586

587
    /**
588
     * @hidden
589
     */
590
    public get resolveRowEditContainer() {
UNCOV
591
        if (this.parentIsland && this.parentIsland.rowEditCustom) {
×
592
            return this.parentIsland.rowEditContainer;
×
593
        }
UNCOV
594
        return this.rowEditContainer;
×
595
    }
596

597
    /**
598
     * @hidden
599
     */
600
    public get resolveRowEditActions() {
UNCOV
601
        return this.parentIsland ? this.parentIsland.rowEditActionsTemplate : this.rowEditActionsTemplate;
×
602
    }
603

604
    /**
605
     * @hidden
606
     */
607
    public get resolveRowEditText() {
UNCOV
608
        return this.parentIsland ? this.parentIsland.rowEditTextTemplate : this.rowEditTextTemplate;
×
609
    }
610

611
    /** @hidden */
612
    public override hideActionStrip() {
UNCOV
613
        if (!this.parent) {
×
614
            // hide child layout actions strips when
615
            // moving outside root grid.
UNCOV
616
            super.hideActionStrip();
×
UNCOV
617
            this.allLayoutList.forEach(ri => {
×
UNCOV
618
                ri.actionStrip?.hide();
×
619
            });
620
        }
621
    }
622

623
    /**
624
     * @hidden
625
     */
626
    public override get parentRowOutletDirective() {
627
        // Targeting parent outlet in order to prevent hiding when outlet
628
        // is present at a child grid and is attached to a row.
UNCOV
629
        return this.parent ? this.parent.rowOutletDirective : this.outlet;
×
630
    }
631

632
    /**
633
     * @hidden
634
     */
635
    public override ngOnInit() {
636
        // this.expansionStatesChange.pipe(takeUntil(this.destroy$)).subscribe((value: Map<any, boolean>) => {
637
        //     const res = Array.from(value.entries()).filter(({1: v}) => v === true).map(([k]) => k);
638
        // });
639
        this.batchEditing = !!this.rootGrid.batchEditing;
1✔
640
        if (this.rootGrid !== this) {
1!
UNCOV
641
            this.rootGrid.batchEditingChange.pipe(takeUntil(this.destroy$)).subscribe((val: boolean) => {
×
642
                this.batchEditing = val;
×
643
            });
644
        }
645
        super.ngOnInit();
1✔
646
    }
647

648
    /**
649
     * @hidden
650
     */
651
    public override ngAfterViewInit() {
652
        super.ngAfterViewInit();
1✔
653
        this.verticalScrollContainer.beforeViewDestroyed.pipe(takeUntil(this.destroy$)).subscribe((view) => {
1✔
UNCOV
654
            const rowData = view.context.$implicit;
×
UNCOV
655
            if (this.isChildGridRecord(rowData)) {
×
UNCOV
656
                const cachedData = this.childGridTemplates.get(rowData.rowID);
×
UNCOV
657
                if (cachedData) {
×
UNCOV
658
                    const tmlpOutlet = cachedData.owner;
×
UNCOV
659
                    tmlpOutlet._viewContainerRef.detach(0);
×
660
                }
661
            }
662
        });
663

664
        if (this.parent) {
1!
UNCOV
665
            this.childLayoutKeys = this.parentIsland.children.map((item) => item.key);
×
666
        }
667

668
        this.headSelectorsTemplates = this.parentIsland ?
1!
669
            this.parentIsland.headSelectorsTemplates :
670
            this.headSelectorsTemplates;
671

672
        this.rowSelectorsTemplates = this.parentIsland ?
1!
673
            this.parentIsland.rowSelectorsTemplates :
674
            this.rowSelectorsTemplates;
675
        this.dragIndicatorIconTemplate = this.parentIsland ?
1!
676
            this.parentIsland.dragIndicatorIconTemplate :
677
            this.dragIndicatorIconTemplate;
678
        this.rowExpandedIndicatorTemplate = this.rootGrid.rowExpandedIndicatorTemplate;
1✔
679
        this.rowCollapsedIndicatorTemplate = this.rootGrid.rowCollapsedIndicatorTemplate;
1✔
680
        this.headerCollapsedIndicatorTemplate = this.rootGrid.headerCollapsedIndicatorTemplate;
1✔
681
        this.headerExpandedIndicatorTemplate = this.rootGrid.headerExpandedIndicatorTemplate;
1✔
682
        this.excelStyleHeaderIconTemplate = this.rootGrid.excelStyleHeaderIconTemplate;
1✔
683
        this.sortAscendingHeaderIconTemplate = this.rootGrid.sortAscendingHeaderIconTemplate;
1✔
684
        this.sortDescendingHeaderIconTemplate = this.rootGrid.sortDescendingHeaderIconTemplate;
1✔
685
        this.sortHeaderIconTemplate = this.rootGrid.sortHeaderIconTemplate;
1✔
686
        this.hasChildrenKey = this.parentIsland ?
1!
687
            this.parentIsland.hasChildrenKey || this.rootGrid.hasChildrenKey :
×
688
            this.rootGrid.hasChildrenKey;
689
        this.showExpandAll = this.parentIsland ?
1!
690
            this.parentIsland.showExpandAll : this.rootGrid.showExpandAll;
691
    }
692

693
    /**
694
     * @hidden
695
     */
696
    public override ngAfterContentInit() {
697
        this.updateColumnList(false);
1✔
698
        this.childLayoutKeys = this.parent ?
1!
UNCOV
699
            this.parentIsland.children.map((item) => item.key) :
×
700
            this.childLayoutKeys = this.childLayoutList.map((item) => item.key);
1✔
701
        this.childLayoutList.notifyOnChanges();
1✔
702
        this.childLayoutList.changes.pipe(takeUntil(this.destroy$)).subscribe(() =>
1✔
UNCOV
703
            this.onRowIslandChange()
×
704
        );
705
        super.ngAfterContentInit();
1✔
706
    }
707

708
    /**
709
     * Returns the `RowType` by index.
710
     *
711
     * @example
712
     * ```typescript
713
     * const myRow = this.grid1.getRowByIndex(1);
714
     * ```
715
     * @param index
716
     */
717
    public getRowByIndex(index: number): RowType {
UNCOV
718
        if (index < 0 || index >= this.dataView.length) {
×
UNCOV
719
            return undefined;
×
720
        }
UNCOV
721
        return this.createRow(index);
×
722
    }
723

724
    /**
725
     * Returns the `RowType` by key.
726
     *
727
     * @example
728
     * ```typescript
729
     * const myRow = this.grid1.getRowByKey(1);
730
     * ```
731
     * @param key
732
     */
733
    public getRowByKey(key: any): RowType {
UNCOV
734
        const data = this.dataView;
×
UNCOV
735
        const rec = this.primaryKey ?
×
UNCOV
736
            data.find(record => record[this.primaryKey] === key) :
×
UNCOV
737
            data.find(record => record === key);
×
UNCOV
738
        const index = data.indexOf(rec);
×
UNCOV
739
        if (index < 0 || index > data.length) {
×
UNCOV
740
            return undefined;
×
741
        }
742

UNCOV
743
        return new IgxHierarchicalGridRow(this as any, index, rec);
×
744
    }
745

746
    /**
747
     * @hidden @internal
748
     */
749
    public allRows(): RowType[] {
UNCOV
750
        return this.dataView.map((rec, index) => this.createRow(index));
×
751
    }
752

753
    /**
754
     * Returns the collection of `IgxHierarchicalGridRow`s for current page.
755
     *
756
     * @hidden @internal
757
     */
758
    public dataRows(): RowType[] {
UNCOV
759
        return this.allRows().filter(row => row instanceof IgxHierarchicalGridRow);
×
760
    }
761

762
    /**
763
     * Returns an array of the selected `IgxGridCell`s.
764
     *
765
     * @example
766
     * ```typescript
767
     * const selectedCells = this.grid.selectedCells;
768
     * ```
769
     */
770
    public get selectedCells(): CellType[] {
UNCOV
771
        return this.dataRows().map((row) => row.cells.filter((cell) => cell.selected))
×
UNCOV
772
            .reduce((a, b) => a.concat(b), []);
×
773
    }
774

775
    /**
776
     * Returns a `CellType` object that matches the conditions.
777
     *
778
     * @example
779
     * ```typescript
780
     * const myCell = this.grid1.getCellByColumn(2, "UnitPrice");
781
     * ```
782
     * @param rowIndex
783
     * @param columnField
784
     */
785
    public getCellByColumn(rowIndex: number, columnField: string): CellType {
UNCOV
786
        const row = this.getRowByIndex(rowIndex);
×
UNCOV
787
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
788
        if (row && row instanceof IgxHierarchicalGridRow && column) {
×
UNCOV
789
            return new IgxGridCell(this, rowIndex, column);
×
790
        }
791
    }
792

793
    /**
794
     * Returns a `CellType` object that matches the conditions.
795
     *
796
     * @remarks
797
     * Requires that the primaryKey property is set.
798
     * @example
799
     * ```typescript
800
     * grid.getCellByKey(1, 'index');
801
     * ```
802
     * @param rowSelector match any rowID
803
     * @param columnField
804
     */
805
    public getCellByKey(rowSelector: any, columnField: string): CellType {
UNCOV
806
        const row = this.getRowByKey(rowSelector);
×
UNCOV
807
        const column = this.columns.find((col) => col.field === columnField);
×
UNCOV
808
        if (row && column) {
×
UNCOV
809
            return new IgxGridCell(this, row.index, column);
×
810
        }
811
    }
812

813
    public override pinRow(rowID: any, index?: number): boolean {
UNCOV
814
        const row = this.getRowByKey(rowID);
×
UNCOV
815
        return super.pinRow(rowID, index, row);
×
816
    }
817

818
    /** @hidden @internal */
819
    public setDataInternal(value: any) {
820
        const oldData = this._data;
1✔
821
        this._data = value || [];
1!
822
        this.summaryService.clearSummaryCache();
1✔
823
        if (!this._init) {
1!
UNCOV
824
            this.validation.updateAll(this._data);
×
825
        }
826
        if (this.autoGenerate && this._data.length > 0 && this.shouldRecreateColumns(oldData, this._data)) {
1!
UNCOV
827
            this.setupColumns();
×
UNCOV
828
            this.reflow();
×
829
        }
830
        this.cdr.markForCheck();
1✔
831
        if (this.parent && (this.height === null || this.height.indexOf('%') !== -1)) {
1!
832
            // If the height will change based on how much data there is, recalculate sizes in igxForOf.
UNCOV
833
            this.notifyChanges(true);
×
834
        }
835
    }
836

837
    public override unpinRow(rowID: any): boolean {
UNCOV
838
        const row = this.getRowByKey(rowID);
×
UNCOV
839
        return super.unpinRow(rowID, row);
×
840
    }
841

842
    /**
843
     * @hidden @internal
844
     */
845
    public dataLoading(event) {
UNCOV
846
        this.dataPreLoad.emit(event);
×
847
    }
848

849
    /** @hidden */
850
    public override featureColumnsWidth() {
851
        return super.featureColumnsWidth(this.headerHierarchyExpander);
25✔
852
    }
853

854
    /**
855
     * @hidden
856
     */
857
    public onRowIslandChange() {
858
        if (this.parent) {
1!
UNCOV
859
            this.childLayoutKeys = this.parentIsland.children.filter(item => !(item as any)._destroyed).map((item) => item.key);
×
860
        } else {
861
            this.childLayoutKeys = this.childLayoutList.filter(item => !(item as any)._destroyed).map((item) => item.key);
1✔
862
        }
863
        if (!(this.cdr as any).destroyed) {
1!
UNCOV
864
            this.cdr.detectChanges();
×
865
        }
866
    }
867

868
    /** @hidden @internal **/
869
    public override ngOnDestroy() {
870
        if (!this.parent) {
1✔
871
            this.gridAPI.getChildGrids(true).forEach((grid) => {
1✔
872
                if (!grid.childRow.cdr.destroyed) {
×
873
                    grid.childRow.cdr.destroy();
×
874
                }
875
            });
876
        }
877
        if (this.parent && this.selectionService.activeElement) {
1!
878
            // in case selection is in destroyed child grid, selection should be cleared.
UNCOV
879
            this._clearSeletionHighlights();
×
880
        }
881
        super.ngOnDestroy();
1✔
882
    }
883

884
    /**
885
     * @hidden
886
     */
887
    public isRowHighlighted(rowData) {
UNCOV
888
        return this.highlightedRowID === rowData.rowID;
×
889
    }
890

891
    /**
892
     * @hidden
893
     */
894
    public isHierarchicalRecord(record: any): boolean {
895
        if (this.isGhostRecord(record)) {
50!
UNCOV
896
            record = record.recordRef;
×
897
        }
898
        return this.childLayoutList.length !== 0 && record[this.childLayoutList.first.key];
50✔
899
    }
900

901
    /**
902
     * @hidden
903
     */
904
    public isChildGridRecord(record: any): boolean {
905
        // Can be null when there is defined layout but no child data was found
906
        return record?.childGridsData !== undefined;
55✔
907
    }
908

909
    /**
910
     * @hidden
911
     */
912
    public trackChanges(index, rec) {
913
        if (rec.childGridsData !== undefined) {
65!
914
            // if is child rec
UNCOV
915
            return rec.rowID;
×
916
        }
917
        return rec;
65✔
918
    }
919

920
    /**
921
     * @hidden
922
     */
923
    public getContext(rowData, rowIndex, pinned): any {
924
        if (this.isChildGridRecord(rowData)) {
50!
UNCOV
925
            const cachedData = this.childGridTemplates.get(rowData.rowID);
×
UNCOV
926
            if (cachedData) {
×
UNCOV
927
                const view = cachedData.view;
×
UNCOV
928
                const tmlpOutlet = cachedData.owner;
×
UNCOV
929
                return {
×
930
                    $implicit: rowData,
931
                    moveView: view,
932
                    owner: tmlpOutlet,
933
                    index: this.dataView.indexOf(rowData)
934
                };
935
            } else {
936
                // child rows contain unique grids, hence should have unique templates
UNCOV
937
                return {
×
938
                    $implicit: rowData,
939
                    templateID: {
940
                        type: 'childRow',
941
                        id: rowData.rowID
942
                    },
943
                    index: this.dataView.indexOf(rowData)
944
                };
945
            }
946
        } else {
947
            return {
50✔
948
                $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData,
50!
949
                templateID: {
950
                    type: 'dataRow',
951
                    id: null
952
                },
953
                index: this.getDataViewIndex(rowIndex, pinned),
954
                disabled: this.isGhostRecord(rowData)
955
            };
956
        }
957
    }
958

959
    /**
960
     * @hidden
961
     */
962
    public get rootGrid(): GridType {
963
        let currGrid = this as IgxHierarchicalGridComponent;
781✔
964
        while (currGrid.parent) {
781✔
UNCOV
965
            currGrid = currGrid.parent;
×
966
        }
967
        return currGrid;
781✔
968
    }
969

970
    /**
971
     * @hidden
972
     */
973
    public get iconTemplate() {
974
        const expanded = this.hasExpandedRecords() && this.hasExpandableChildren;
13!
975
        if (!expanded && this.showExpandAll) {
13!
UNCOV
976
            return this.headerCollapsedIndicatorTemplate || this.defaultCollapsedTemplate;
×
977
        } else {
978
            return this.headerExpandedIndicatorTemplate || this.defaultExpandedTemplate;
13✔
979
        }
980
    }
981

982
    /**
983
     * @hidden
984
     * @internal
985
     */
986
    public override getDragGhostCustomTemplate(): TemplateRef<any> {
UNCOV
987
        if (this.parentIsland) {
×
UNCOV
988
            return this.parentIsland.getDragGhostCustomTemplate();
×
989
        }
UNCOV
990
        return super.getDragGhostCustomTemplate();
×
991
    }
992

993
    /**
994
     * @hidden
995
     * Gets the visible content height that includes header + tbody + footer.
996
     * For hierarchical child grid it may be scrolled and not fully visible.
997
     */
998
    public override getVisibleContentHeight() {
999
        let height = super.getVisibleContentHeight();
×
1000
        if (this.parent) {
×
1001
            const rootHeight = this.rootGrid.getVisibleContentHeight();
×
1002
            const topDiff = this.nativeElement.getBoundingClientRect().top - this.rootGrid.nativeElement.getBoundingClientRect().top;
×
1003
            height = rootHeight - topDiff > height ? height : rootHeight - topDiff;
×
1004
        }
1005
        return height;
×
1006
    }
1007

1008
    /**
1009
     * @hidden
1010
     */
1011
    public toggleAll() {
UNCOV
1012
        const expanded = this.hasExpandedRecords() && this.hasExpandableChildren;
×
UNCOV
1013
        if (!expanded && this.showExpandAll) {
×
1014
            this.expandAll();
×
1015
        } else {
UNCOV
1016
            this.collapseAll();
×
1017
        }
1018
    }
1019

1020

1021
    /**
1022
     * @hidden
1023
     * @internal
1024
     */
1025
    public hasExpandedRecords() {
1026
        if (this.expandChildren) {
26!
UNCOV
1027
            return true;
×
1028
        }
1029
        let hasExpandedEntry = false;
26✔
1030
        this.expansionStates.forEach(value => {
26✔
UNCOV
1031
            if (value) {
×
UNCOV
1032
                hasExpandedEntry = value;
×
1033
            }
1034
        });
1035
        return hasExpandedEntry;
26✔
1036
    }
1037

1038
    public override getDefaultExpandState(record: any) {
1039
        if (this.hasChildrenKey && !record[this.hasChildrenKey]) {
109!
UNCOV
1040
            return false;
×
1041
        }
1042
        return this.expandChildren;
109✔
1043

1044
    }
1045

1046
    /**
1047
     * @hidden
1048
     */
1049
    public isExpanded(record: any): boolean {
1050
        return this.gridAPI.get_row_expansion_state(record);
×
1051
    }
1052

1053
    /**
1054
     * @hidden
1055
     */
1056
    public viewCreatedHandler(args) {
1057
        if (this.isChildGridRecord(args.context.$implicit)) {
5!
UNCOV
1058
            const key = args.context.$implicit.rowID;
×
UNCOV
1059
            this.childGridTemplates.set(key, args);
×
1060
        }
1061
    }
1062

1063
    /**
1064
     * @hidden
1065
     */
1066
    public viewMovedHandler(args) {
UNCOV
1067
        if (this.isChildGridRecord(args.context.$implicit)) {
×
1068
            // view was moved, update owner in cache
UNCOV
1069
            const key = args.context.$implicit.rowID;
×
UNCOV
1070
            const cachedData = this.childGridTemplates.get(key);
×
UNCOV
1071
            cachedData.owner = args.owner;
×
1072

UNCOV
1073
            this.childLayoutList.forEach((layout) => {
×
UNCOV
1074
                const relatedGrid = this.gridAPI.getChildGridByID(layout.key, args.context.$implicit.rowID);
×
UNCOV
1075
                if (relatedGrid && relatedGrid.updateOnRender) {
×
1076
                    // Detect changes if `expandChildren` has changed when the grid wasn't visible. This is for performance reasons.
UNCOV
1077
                    relatedGrid.notifyChanges(true);
×
UNCOV
1078
                    relatedGrid.updateOnRender = false;
×
1079
                }
1080
            });
1081
        }
1082
    }
1083

1084
    /** @hidden @internal **/
1085
    public onContainerScroll() {
1086
        this.hideOverlays();
×
1087
    }
1088

1089
    /**
1090
     * @hidden
1091
     */
1092
    public createRow(index: number, data?: any): RowType {
1093
        let row: RowType;
UNCOV
1094
        const dataIndex = this._getDataViewIndex(index);
×
UNCOV
1095
        const rec: any = data ?? this.dataView[dataIndex];
×
1096

UNCOV
1097
        if (!row && rec && !rec.childGridsData) {
×
UNCOV
1098
            row = new IgxHierarchicalGridRow(this as any, index, rec);
×
1099
        }
1100

UNCOV
1101
        return row;
×
1102
    }
1103

1104
    /** @hidden @internal */
1105
    public getChildGrids(inDeph?: boolean) {
UNCOV
1106
        return this.gridAPI.getChildGrids(inDeph);
×
1107
    }
1108

1109
    protected override generateDataFields(data: any[]): string[] {
UNCOV
1110
        return super.generateDataFields(data).filter((field) => {
×
UNCOV
1111
            const layoutsList = this.parentIsland ? this.parentIsland.children : this.childLayoutList;
×
UNCOV
1112
            const keys = layoutsList.map((item) => item.key);
×
UNCOV
1113
            return keys.indexOf(field) === -1;
×
1114
        });
1115
    }
1116

1117
    protected resizeNotifyHandler() {
1118
        // do not trigger reflow if element is detached or if it is child grid.
1119
        if (this.nativeElement?.isConnected && !this.parent) {
×
1120
            this.notifyChanges(true);
×
1121
        }
1122
    }
1123

1124
    /**
1125
     * @hidden
1126
     */
1127
    protected override initColumns(collection: IgxColumnComponent[], cb: (args: any) => void = null) {
×
1128
        if (this.hasColumnLayouts) {
2!
1129
            // invalid configuration - hierarchical grid should not allow column layouts
1130
            // remove column layouts
1131
            const nonColumnLayoutColumns = this.columns.filter((col) => !col.columnLayout && !col.columnLayoutChild);
×
1132
            this.updateColumns(nonColumnLayoutColumns);
×
1133
        }
1134
        super.initColumns(collection, cb);
2✔
1135
    }
1136

1137

1138
    protected override setupColumns() {
1139
        if (this.parentIsland && this.parentIsland.childColumns.length > 0 && !this.autoGenerate) {
1!
UNCOV
1140
            this.createColumnsList(this.parentIsland.childColumns.toArray());
×
1141
        } else {
1142
            super.setupColumns();
1✔
1143
        }
1144
    }
1145

1146
    protected override getColumnList() {
1147
        const childLayouts = this.parent ? this.childLayoutList : this.allLayoutList;
1!
1148
        const nestedColumns = childLayouts.map((layout) => layout.columnList.toArray());
2✔
1149
        const colsArray = [].concat.apply([], nestedColumns);
1✔
1150
        if (colsArray.length > 0) {
1!
1151
            const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
9✔
1152
            return topCols;
1✔
1153
        } else {
UNCOV
1154
            return this.columnList.toArray()
×
1155
        }
1156
    }
1157

1158
    protected override onColumnsChanged() {
UNCOV
1159
        Promise.resolve().then(() => {
×
UNCOV
1160
            this.updateColumnList();
×
1161
        });
1162
    }
1163

1164
    protected override _shouldAutoSize(renderedHeight) {
UNCOV
1165
        if (this.isPercentHeight && this.parent) {
×
UNCOV
1166
            return true;
×
1167
        }
1168
        return super._shouldAutoSize(renderedHeight);
×
1169
    }
1170

1171
    private updateColumnList(recalcColSizes = true) {
×
1172
        const childLayouts = this.parent ? this.childLayoutList : this.allLayoutList;
1!
1173
        const nestedColumns = childLayouts.map((layout) => layout.columnList.toArray());
2✔
1174
        const colsArray = [].concat.apply([], nestedColumns);
1✔
1175
        const colLength = this.columns.length;
1✔
1176
        const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
9✔
1177
        if (topCols.length > 0) {
1✔
1178
            this.initColumns(topCols, (col: IgxColumnComponent) => this.columnInit.emit(col));
3✔
1179
            if (recalcColSizes && this.columns.length !== colLength) {
1!
UNCOV
1180
                this.calculateGridSizes(false);
×
1181
            }
1182
        }
1183
    }
1184

1185
    private _clearSeletionHighlights() {
UNCOV
1186
        [this.rootGrid, ...this.rootGrid.getChildGrids(true)].forEach(grid => {
×
UNCOV
1187
            grid.selectionService.clear();
×
UNCOV
1188
            grid.selectionService.activeElement = null;
×
UNCOV
1189
            grid.nativeElement.classList.remove('igx-grid__tr--highlighted');
×
UNCOV
1190
            grid.highlightedRowID = null;
×
UNCOV
1191
            grid.cdr.markForCheck();
×
1192
        });
1193
    }
1194
}
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

© 2025 Coveralls, Inc