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

IgniteUI / igniteui-angular / 6405949148

04 Oct 2023 12:27PM CUT coverage: 92.26% (+0.02%) from 92.238%
6405949148

push

github

web-flow
chore(grids): bump watermark version (#13435)

Co-authored-by: Konstantin Dinev <kdinev@mail.bw.edu>

15308 of 17987 branches covered (0.0%)

26844 of 29096 relevant lines covered (92.26%)

29550.57 hits per line

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

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

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

67
let NEXT_ID = 0;
68

69
export interface HierarchicalStateRecord {
70
    rowID: any;
71
}
72

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

84
    /**
4,674✔
85
     * @hidden
86
     */
87
    public get parentHasScroll() {
88
        return !this.parentGrid.verticalScrollContainer.dc.instance.notVirtual;
89
    }
90

91

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

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

111
    public set data(value: any) {
112
        this._data = value;
113
        if (this.hGrid) {
587✔
114
            this.hGrid.data = this._data.childGridsData[this.layout.key];
587✔
115
        }
587✔
116
    }
587✔
117
    
50✔
118
    /**
119
     * The index of the row.
587✔
120
     *
587✔
121
     * ```typescript
615✔
122
     * // get the index of the second selected row
123
     * let selectedRowIndex = this.grid.selectedRows[1].index;
587✔
124
     * ```
587✔
125
     */
587✔
126
    @Input()
127
    public index: number;
587✔
128

587✔
129
    @ViewChild('container', {read: ViewContainerRef, static: true})
130
    public container: ViewContainerRef;
131

132
    /**
133
     * @hidden
134
     */
135
    public hGrid: IgxHierarchicalGridComponent;
136

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

159
    @HostBinding('attr.aria-level')
160
    public get level() {
28,763✔
161
        return this.layout.level;
28,176✔
162
    }
27,589✔
163

4,320!
164
    /**
×
165
     * The native DOM element representing the row. Could be null in certain environments.
166
     *
4,320✔
167
     * ```typescript
4,320✔
168
     * // get the nativeElement of the second selected row
169
     * let selectedRowNativeElement = this.grid.selectedRows[1].nativeElement;
170
     * ```
171
     */
172
    public get nativeElement() {
173
        return this.element.nativeElement;
665✔
174
    }
1,967!
175

1,967✔
176
    /**
177
     * Returns whether the row is expanded.
178
     * ```typescript
179
     * const RowExpanded = this.grid1.rowList.first.expanded;
2✔
180
     * ```
181
     */
182
    public expanded = false;
183

184
    private _data: any;
2✔
185

186
    constructor(
187
        @Inject(IGX_GRID_SERVICE_BASE) public readonly gridAPI: IgxHierarchicalGridAPIService,
188
        public element: ElementRef<HTMLElement>,
189
        public cdr: ChangeDetectorRef) { }
190

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

217
    /**
218
     * @hidden
821✔
219
     */
220
    public ngAfterViewInit() {
221
        this.hGrid.childLayoutList = this.layout.children;
222
        const layouts = this.hGrid.childLayoutList.toArray();
821✔
223
        layouts.forEach((l) => this.hGrid.gridAPI.registerChildRowIsland(l));
821✔
224
        this.parentGrid.gridAPI.registerChildGrid(this.data.rowID, this.layout.key, this.hGrid);
821✔
225
        this.layout.rowIslandAPI.registerChildGrid(this.data.rowID, this.hGrid);
226

227
        this.layout.gridInitialized.emit({
228
            owner: this.layout,
26,600✔
229
            parentID: this.data.rowID,
230
            grid: this.hGrid
231
        });
860,499✔
232

233
        this.hGrid.cdr.detectChanges();
234
    }
×
235

236
    private setupEventEmitters() {
237
        const destructor = takeUntil(this.hGrid.destroy$);
945✔
238

945✔
239
        const mirror = reflectComponentType(IgxGridComponent);
945✔
240
        // exclude outputs related to two-way binding functionality
124✔
241
        const inputNames = mirror.inputs.map(input => input.propName);
242
        const outputs = mirror.outputs.filter(o => {
945✔
243
            const matchingInputPropName = o.propName.slice(0, o.propName.indexOf('Change'));
4✔
244
            return inputNames.indexOf(matchingInputPropName) === -1;
4✔
245
        });
246

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

261

47,954✔
262
    private _handleLayoutChanges(changes: SimpleChanges) {
263
        for (const change in changes) {
264
            if (changes.hasOwnProperty(change)) {
265
                this.hGrid[change] = changes[change].currentValue;
651,653✔
266
            }
651,653✔
267
        }
268
    }
269
}
270

2✔
271
@Component({
272
    changeDetection: ChangeDetectionStrategy.OnPush,
273
    selector: 'igx-hierarchical-grid',
274
    templateUrl: 'hierarchical-grid.component.html',
275
    providers: [
×
276
        IgxGridCRUDService,
277
        IgxGridValidationService,
278
        IgxGridSelectionService,
109✔
279
        { provide: IGX_GRID_SERVICE_BASE, useClass: IgxHierarchicalGridAPIService },
280
        { provide: IGX_GRID_BASE, useExisting: IgxHierarchicalGridComponent },
281
        IgxGridSummaryService,
93✔
282
        IgxFilteringService,
93✔
283
        IgxHierarchicalGridNavigationService,
284
        IgxColumnResizingService,
285
        IgxForOfSyncService,
286
        IgxForOfScrollSyncService,
287
        IgxRowIslandAPIService
288
    ],
289
    standalone: true,
290
    imports: [
291
        NgIf,
292
        NgClass,
293
        NgFor,
294
        NgTemplateOutlet,
111,744✔
295
        NgStyle,
296
        IgxGridHeaderRowComponent,
297
        IgxGridBodyDirective,
298
        IgxGridDragSelectDirective,
299
        IgxColumnMovingDropDirective,
300
        IgxGridForOfDirective,
301
        IgxTemplateOutletDirective,
302
        IgxHierarchicalRowComponent,
303
        IgxOverlayOutletDirective,
304
        IgxToggleDirective,
305
        IgxCircularProgressBarComponent,
306
        IgxSnackbarComponent,
×
307
        IgxSummaryRowComponent,
×
308
        IgxButtonDirective,
309
        IgxRippleDirective,
×
310
        IgxIconComponent,
311
        IgxRowEditTabStopDirective,
312
        IgxGridColumnResizerComponent,
313
        IgxChildGridRowComponent,
314
        IgxGridSortingPipe,
315
        IgxGridFilteringPipe,
22,029✔
316
        IgxGridTransactionPipe,
317
        IgxHasVisibleColumnsPipe,
318
        IgxGridRowPinningPipe,
319
        IgxGridAddRowPipe,
320
        IgxGridRowClassesPipe,
321
        IgxGridRowStylesPipe,
1,218!
322
        IgxSummaryDataPipe,
×
323
        IgxGridHierarchicalPipe,
324
        IgxGridHierarchicalPagingPipe
1,218✔
325
    ],
326
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
327
})
328
export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseDirective
329
    implements GridType, AfterViewInit, AfterContentInit, OnInit, OnDestroy, DoCheck {
330

1,218✔
331
    /**
332
     * @hidden @internal
333
     */
334
    @HostBinding('attr.role')
335
    public role = 'grid';
336

1,207✔
337
    /**
338
     * @hidden
339
     */
340
    @ContentChildren(IgxRowIslandComponent, { read: IgxRowIslandComponent, descendants: false })
1!
341
    public childLayoutList: QueryList<IgxRowIslandComponent>;
342

343
    /**
1✔
344
     * @hidden
1✔
345
     */
2✔
346
    @ContentChildren(IgxRowIslandComponent, { read: IgxRowIslandComponent, descendants: true })
347
    public allLayoutList: QueryList<IgxRowIslandComponent>;
348

349
    /** @hidden @internal */
350
    @ContentChildren(IgxPaginatorComponent, { descendants: true })
351
    public paginatorList: QueryList<IgxPaginatorComponent>;
352

353
    /** @hidden @internal */
354
    @ViewChild('toolbarOutlet', { read: ViewContainerRef })
355
    public toolbarOutlet: ViewContainerRef;
1!
356

357
    /** @hidden @internal */
358
    @ViewChild('paginatorOutlet', { read: ViewContainerRef })
359
    public paginatorOutlet: ViewContainerRef;
360
    /**
361
     * @hidden
362
     */
363
    @ViewChildren(IgxTemplateOutletDirective, { read: IgxTemplateOutletDirective })
364
    public templateOutlets: QueryList<any>;
821✔
365

821✔
366
    /**
587✔
367
     * @hidden
×
368
     */
369
    @ViewChildren(IgxChildGridRowComponent)
370
    public hierarchicalRows: QueryList<IgxChildGridRowComponent>;
821✔
371

372
    @ViewChild('hierarchical_record_template', { read: TemplateRef, static: true })
373
    protected hierarchicalRecordTemplate: TemplateRef<any>;
374

375
    @ViewChild('child_record_template', { read: TemplateRef, static: true })
376
    protected childTemplate: TemplateRef<any>;
821✔
377

821✔
378
    // @ViewChild('headerHierarchyExpander', { read: ElementRef, static: true })
631✔
379
    protected get headerHierarchyExpander() {
631✔
380
        return this.theadRow?.headerHierarchyExpander;
51✔
381
    }
51✔
382

43✔
383
    /**
43✔
384
     * @hidden
385
     */
386
    public childLayoutKeys = [];
387

821✔
388
    /**
587✔
389
     * @hidden
587✔
390
     */
587✔
391
    public highlightedRowID = null;
4✔
392

4✔
393
    /**
4✔
394
     * @hidden
4✔
395
     */
396
    public updateOnRender = false;
587✔
397

398
    /**
821✔
399
     * @hidden
821✔
400
     */
401
    public parent: IgxHierarchicalGridComponent = null;
402

821✔
403
    /**
404
     * @hidden
405
     */
821✔
406
    public childRow: IgxChildGridRowComponent;
407

408
    private _data;
821✔
409
    private h_id = `igx-hierarchical-grid-${NEXT_ID++}`;
821✔
410
    private childGridTemplates: Map<any, any> = new Map();
821✔
411

821✔
412
    /**
821✔
413
     * Gets/Sets the value of the `id` attribute.
821✔
414
     *
821✔
415
     * @remarks
821✔
416
     * If not provided it will be automatically generated.
821✔
417
     * @example
1,174✔
418
     * ```html
419
     * <igx-hierarchical-grid [id]="'igx-hgrid-1'" [data]="Data" [autoGenerate]="true"></igx-hierarchical-grid>
821✔
420
     * ```
421
     */
422
    @HostBinding('attr.id')
423
    @Input()
424
    public get id(): string {
425
        return this.h_id;
426
    }
821✔
427
    public set id(value: string) {
821✔
428
        this.h_id = value;
415✔
429
    }
266✔
430

821✔
431
    /**
821✔
432
     * An @Input property that lets you fill the `IgxHierarchicalGridComponent` with an array of data.
821✔
433
     * ```html
434
     * <igx-hierarchical-grid [data]="Data" [autoGenerate]="true"></igx-hierarchical-grid>
435
     * ```
436
     *
437
     * @memberof IgxHierarchicalGridComponent
438
     */
439
    @Input()
440
    public set data(value: any[] | null) {
441
        this._data = value || [];
442
        this.summaryService.clearSummaryCache();
443
        if (!this._init) {
444
            this.validation.updateAll(this._data);
50✔
445
        }
1✔
446
        if (this.shouldGenerate) {
447
            this.setupColumns();
49✔
448
            this.reflow();
449
        }
450
        this.cdr.markForCheck();
451
        if (this.parent && (this.height === null || this.height.indexOf('%') !== -1)) {
452
            // If the height will change based on how much data there is, recalculate sizes in igxForOf.
453
            this.notifyChanges(true);
454
        }
455
    }
456

457
    /**
458
     * Returns an array of data set to the `IgxHierarchicalGridComponent`.
459
     * ```typescript
27✔
460
     * let filteredData = this.grid.filteredData;
27✔
461
     * ```
106✔
462
     *
20✔
463
     * @memberof IgxHierarchicalGridComponent
27✔
464
     */
27✔
465
    public get data(): any[] | null {
1✔
466
        return this._data;
467
    }
26✔
468

469
    /** @hidden @internal */
470
    public override get paginator() {
471
        const id = this.id;
472
        return (!this.parentIsland && this.paginationComponents?.first) || this.rootGrid.paginatorList?.find((pg) =>
473
            pg.nativeElement.offsetParent?.id === id);
34✔
474
    }
475

476
    /** @hidden @internal */
477
    public override get excelStyleFilteringComponent() : IgxGridExcelStyleFilteringComponent {
478
        return this.parentIsland ?
479
            this.parentIsland.excelStyleFilteringComponents.first :
480
            super.excelStyleFilteringComponent;
481
    }
34✔
482

483
    /**
484
     * Gets/Sets the total number of records in the data source.
485
     *
486
     * @remarks
487
     * This property is required for remote grid virtualization to function when it is bound to remote data.
488
     * @example
489
     * ```typescript
490
     * const itemCount = this.grid1.totalItemCount;
491
     * this.grid1.totalItemCount = 55;
492
     * ```
168✔
493
     */
34✔
494
    @Input()
495
    public set totalItemCount(count) {
496
        this.verticalScrollContainer.totalItemCount = count;
497
    }
498

499
    public get totalItemCount() {
500
        return this.verticalScrollContainer.totalItemCount;
501
    }
502

503
    /**
504
     * Sets if all immediate children of the `IgxHierarchicalGridComponent` should be expanded/collapsed.
505
     * Defult value is false.
506
     * ```html
13✔
507
     * <igx-hierarchical-grid [id]="'igx-grid-1'" [data]="Data" [autoGenerate]="true" [expandChildren]="true"></igx-hierarchical-grid>
17✔
508
     * ```
13!
509
     *
13✔
510
     * @memberof IgxHierarchicalGridComponent
511
     */
512
    @Input()
513
    public set expandChildren(value: boolean) {
514
        this._defaultExpandState = value;
515
        this.expansionStates = new Map<any, boolean>();
516
    }
517

518
    /**
519
     * Gets if all immediate children of the `IgxHierarchicalGridComponent` previously have been set to be expanded/collapsed.
520
     * If previously set and some rows have been manually expanded/collapsed it will still return the last set value.
521
     * ```typescript
522
     * const expanded = this.grid.expandChildren;
523
     * ```
524
     *
525
     * @memberof IgxHierarchicalGridComponent
1✔
526
     */
7✔
527
    public get expandChildren(): boolean {
1!
528
        return this._defaultExpandState;
1✔
529
    }
530

531
    /**
532
     * Gets the unique identifier of the parent row. It may be a `string` or `number` if `primaryKey` of the
18✔
533
     * parent grid is set or an object reference of the parent record otherwise.
18✔
534
     * ```typescript
535
     * const foreignKey = this.grid.foreignKey;
536
     * ```
1✔
537
     *
1✔
538
     * @memberof IgxHierarchicalGridComponent
539
     */
540
    public get foreignKey() {
541
        if (!this.parent) {
542
            return null;
543
        }
47✔
544
        return this.parent.gridAPI.getParentRowId(this);
545
    }
546

547
    /**
26,596✔
548
     * @hidden
549
     */
550
    public get hasExpandableChildren() {
551
        return !!this.childLayoutKeys.length;
552
    }
553

677✔
554
    /**
406✔
555
     * @hidden
556
     */
557
    public get resolveRowEditContainer() {
338✔
558
        if (this.parentIsland && this.parentIsland.rowEditCustom) {
559
            return this.parentIsland.rowEditContainer;
677✔
560
        }
78✔
561
        return this.rowEditContainer;
562
    }
563

564
    /**
565
     * @hidden
807✔
566
     */
234✔
567
    public get resolveRowEditActions() {
×
568
        return this.parentIsland ? this.parentIsland.rowEditActionsTemplate : this.rowEditActionsTemplate;
×
569
    }
570

571
    /**
572
     * @hidden
807✔
573
     */
574
    public get resolveRowEditText() {
31✔
575
        return this.parentIsland ? this.parentIsland.rowEditTextTemplate : this.rowEditTextTemplate;
576
    }
807✔
577

578
    /** @hidden */
579
    public override hideActionStrip() {
580
        if (!this.parent) {
581
            // hide child layout actions strips when
582
            // moving outside root grid.
4,099✔
583
            super.hideActionStrip();
584
            this.allLayoutList.forEach(ri => {
585
                ri.actionStrip?.hide();
586
            });
587
        }
588
    }
44,658✔
589

93✔
590
    /**
591
     * @hidden
44,658✔
592
     */
593
    public override get parentRowOutletDirective() {
594
        // Targeting parent outlet in order to prevent hiding when outlet
595
        // is present at a child grid and is attached to a row.
596
        return this.parent ? this.parent.rowOutletDirective : this.outlet;
597
    }
598

63,080✔
599
    /**
600
     * @hidden
601
     */
602
    public override ngOnInit() {
603
        // this.expansionStatesChange.pipe(takeUntil(this.destroy$)).subscribe((value: Map<any, boolean>) => {
604
        //     const res = Array.from(value.entries()).filter(({1: v}) => v === true).map(([k]) => k);
1,131,672✔
605
        // });
606
        this.batchEditing = !!this.rootGrid.batchEditing;
15,132✔
607
        if (this.rootGrid !== this) {
608
            this.rootGrid.batchEditingChange.pipe(takeUntil(this.destroy$)).subscribe((val: boolean) => {
1,116,540✔
609
                this.batchEditing = val;
610
            });
611
        }
612
        super.ngOnInit();
613
    }
614

44,759✔
615
    /**
4,099✔
616
     * @hidden
4,099✔
617
     */
3,554✔
618
    public override ngAfterViewInit() {
3,554✔
619
        super.ngAfterViewInit();
3,554✔
620
        this.verticalScrollContainer.beforeViewDestroyed.pipe(takeUntil(this.destroy$)).subscribe((view) => {
621
            const rowData = view.context.$implicit;
622
            if (this.isChildGridRecord(rowData)) {
623
                const cachedData = this.childGridTemplates.get(rowData.rowID);
624
                if (cachedData) {
625
                    const tmlpOutlet = cachedData.owner;
626
                    tmlpOutlet._viewContainerRef.detach(0);
627
                }
628
            }
545✔
629
        });
630

631
        if (this.parent) {
632
            this._displayDensity = this.rootGrid.displayDensity;
633
            this.summaryService.summaryHeight = 0;
634
            this.rootGrid.densityChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
635
                this._displayDensity = this.rootGrid.displayDensity;
636
                this.summaryService.summaryHeight = 0;
637
                this.notifyChanges(true);
638
                this.cdr.markForCheck();
639
            });
40,660✔
640
            this.childLayoutKeys = this.parentIsland.children.map((item) => item.key);
40,660✔
641
        }
642

643
        this.actionStrip = this.parentIsland ? this.parentIsland.actionStrip : this.actionStrip;
644

645
        this.headSelectorsTemplates = this.parentIsland ?
646
            this.parentIsland.headSelectorsTemplates :
647
            this.headSelectorsTemplates;
648

649
        this.rowSelectorsTemplates = this.parentIsland ?
650
            this.parentIsland.rowSelectorsTemplates :
651
            this.rowSelectorsTemplates;
652
        this.dragIndicatorIconTemplate = this.parentIsland ?
653
            this.parentIsland.dragIndicatorIconTemplate :
654
            this.dragIndicatorIconTemplate;
715,472✔
655
        this.rowExpandedIndicatorTemplate = this.rootGrid.rowExpandedIndicatorTemplate;
715,472✔
656
        this.rowCollapsedIndicatorTemplate = this.rootGrid.rowCollapsedIndicatorTemplate;
542,056✔
657
        this.headerCollapsedIndicatorTemplate = this.rootGrid.headerCollapsedIndicatorTemplate;
658
        this.headerExpandedIndicatorTemplate = this.rootGrid.headerExpandedIndicatorTemplate;
715,472✔
659
        this.excelStyleHeaderIconTemplate = this.rootGrid.excelStyleHeaderIconTemplate;
660
        this.sortAscendingHeaderIconTemplate = this.rootGrid.sortAscendingHeaderIconTemplate;
661
        this.sortDescendingHeaderIconTemplate = this.rootGrid.sortDescendingHeaderIconTemplate;
662
        this.sortHeaderIconTemplate = this.rootGrid.sortHeaderIconTemplate;
663
        this.hasChildrenKey = this.parentIsland ?
664
            this.parentIsland.hasChildrenKey || this.rootGrid.hasChildrenKey :
9,005✔
665
            this.rootGrid.hasChildrenKey;
9,005✔
666
        this.showExpandAll = this.parentIsland ?
61✔
667
            this.parentIsland.showExpandAll : this.rootGrid.showExpandAll;
668
    }
669

8,944✔
670
    /**
671
     * @hidden
672
     */
673
    public override ngAfterContentInit() {
674
        this.updateColumnList(false);
675
        this.childLayoutKeys = this.parent ?
676
            this.parentIsland.children.map((item) => item.key) :
677
            this.childLayoutKeys = this.childLayoutList.map((item) => item.key);
103✔
678
        this.childLayoutList.notifyOnChanges();
45✔
679
        this.childLayoutList.changes.pipe(takeUntil(this.destroy$)).subscribe(() =>
680
            this.onRowIslandChange()
58✔
681
        );
682
        super.ngAfterContentInit();
683
    }
684

685
    /**
686
     * Returns the `RowType` by index.
687
     *
688
     * @example
×
689
     * ```typescript
×
690
     * const myRow = this.grid1.getRowByIndex(1);
×
691
     * ```
×
692
     * @param index
×
693
     */
694
    public getRowByIndex(index: number): RowType {
×
695
        if (index < 0 || index >= this.dataView.length) {
696
            return undefined;
697
        }
698
        return this.createRow(index);
699
    }
700

1✔
701
    /**
1!
702
     * Returns the `RowType` by key.
×
703
     *
704
     * @example
705
     * ```typescript
1✔
706
     * const myRow = this.grid1.getRowByKey(1);
707
     * ```
708
     * @param key
709
     */
710
    public getRowByKey(key: any): RowType {
711
        const data = this.dataView;
712
        const rec = this.primaryKey ?
713
            data.find(record => record[this.primaryKey] === key) :
18,006✔
714
            data.find(record => record === key);
1,954✔
715
        const index = data.indexOf(rec);
716
        if (index < 0 || index > data.length) {
16,052✔
717
            return undefined;
16,052✔
718
        }
2,502✔
719

2,337✔
720
        return new IgxHierarchicalGridRow(this as any, index, rec);
721
    }
722

16,052✔
723
    /**
724
     * @hidden @internal
725
     */
93,771✔
726
    public allRows(): RowType[] {
33✔
727
        return this.dataView.map((rec, index) => this.createRow(index));
728
    }
93,738✔
729

730
    /**
731
     * Returns the collection of `IgxHierarchicalGridRow`s for current page.
732
     *
733
     * @hidden @internal
734
     */
×
735
    public dataRows(): RowType[] {
736
        return this.allRows().filter(row => row instanceof IgxHierarchicalGridRow);
737
    }
738

739
    /**
740
     * Returns an array of the selected `IgxGridCell`s.
6,026✔
741
     *
527✔
742
     * @example
527✔
743
     * ```typescript
744
     * const selectedCells = this.grid.selectedCells;
745
     * ```
746
     */
747
    public get selectedCells(): CellType[] {
748
        return this.dataRows().map((row) => row.cells.filter((cell) => cell.selected))
749
            .reduce((a, b) => a.concat(b), []);
40!
750
    }
751

40✔
752
    /**
40✔
753
     * Returns a `CellType` object that matches the conditions.
40✔
754
     *
40✔
755
     * @example
40✔
756
     * ```typescript
40✔
757
     * const myCell = this.grid1.getCellByColumn(2, "UnitPrice");
758
     * ```
1✔
759
     * @param rowIndex
1✔
760
     * @param columnField
761
     */
762
    public getCellByColumn(rowIndex: number, columnField: string): CellType {
763
        const row = this.getRowByIndex(rowIndex);
764
        const column = this.columns.find((col) => col.field === columnField);
765
        if (row && row instanceof IgxHierarchicalGridRow && column) {
766
            return new IgxGridCell(this, rowIndex, column);
×
767
        }
768
    }
769

770
    /**
771
     * Returns a `CellType` object that matches the conditions.
772
     *
773
     * @remarks
337✔
774
     * Requires that the primaryKey property is set.
337✔
775
     * @example
337!
776
     * ```typescript
337✔
777
     * grid.getCellByKey(1, 'index');
778
     * ```
337✔
779
     * @param rowSelector match any rowID
780
     * @param columnField
781
     */
782
    public getCellByKey(rowSelector: any, columnField: string): CellType {
139✔
783
        const row = this.getRowByKey(rowSelector);
784
        const column = this.columns.find((col) => col.field === columnField);
785
        if (row && column) {
329✔
786
            return new IgxGridCell(this, row.index, column);
2,528✔
787
        }
2,528✔
788
    }
2,528✔
789

790
    public override pinRow(rowID: any, index?: number): boolean {
791
        const row = this.getRowByKey(rowID);
792
        return super.pinRow(rowID, index, row);
793
    }
×
794

×
795
    public override unpinRow(rowID: any): boolean {
796
        const row = this.getRowByKey(rowID);
797
        return super.unpinRow(rowID, row);
798
    }
799

800
    /**
×
801
     * @hidden @internal
504!
802
     */
803
    public dataLoading(event) {
804
        this.dataPreLoad.emit(event);
×
805
    }
×
806

807
    /** @hidden */
504✔
808
    public override featureColumnsWidth() {
809
        return super.featureColumnsWidth(this.headerHierarchyExpander);
810
    }
825✔
811

321✔
812
    /**
813
     * @hidden
814
     */
504✔
815
    public onRowIslandChange() {
816
        if (this.parent) {
817
            this.childLayoutKeys = this.parentIsland.children.filter(item => !(item as any)._destroyed).map((item) => item.key);
818
        } else {
175!
819
            this.childLayoutKeys = this.childLayoutList.filter(item => !(item as any)._destroyed).map((item) => item.key);
351✔
820
        }
175✔
821
        if (!(this.cdr as any).destroyed) {
175✔
822
            this.cdr.detectChanges();
1,687✔
823
        }
168✔
824
    }
825

826
    /** @hidden @internal **/
7✔
827
    public override ngOnDestroy() {
828
        if (!this.parent) {
829
            this.gridAPI.getChildGrids(true).forEach((grid) => {
830
                if (!grid.childRow.cdr.destroyed) {
9✔
831
                    grid.childRow.cdr.destroy();
9✔
832
                }
833
            });
834
        }
835
        if (this.parent && this.selectionService.activeElement) {
539!
836
            // in case selection is in destroyed child grid, selection should be cleared.
539✔
837
            this._clearSeletionHighlights();
838
        }
×
839
        super.ngOnDestroy();
840
    }
9✔
841

830✔
842
    /**
830✔
843
     * @hidden
830✔
844
     */
830✔
845
    public isRowHighlighted(rowData) {
1,793✔
846
        return this.highlightedRowID === rowData.rowID;
830✔
847
    }
183✔
848

183✔
849
    /**
2✔
850
     * @hidden
851
     */
852
    public isHierarchicalRecord(record: any): boolean {
853
        if (this.isGhostRecord(record)) {
854
            record = record.recordRef;
31✔
855
        }
227✔
856
        return this.childLayoutList.length !== 0 && record[this.childLayoutList.first.key];
227✔
857
    }
227✔
858

227✔
859
    /**
227✔
860
     * @hidden
861
     */
862
    public isChildGridRecord(record: any): boolean {
2✔
863
        // Can be null when there is defined layout but no child data was found
864
        return record?.childGridsData !== undefined;
865
    }
866

867
    /**
868
     * @hidden
869
     */
870
    public trackChanges(index, rec) {
871
        if (rec.childGridsData !== undefined) {
872
            // if is child rec
873
            return rec.rowID;
874
        }
875
        return rec;
876
    }
877

878
    /**
879
     * @hidden
2✔
880
     */
881
    public getContext(rowData, rowIndex, pinned): any {
882
        if (this.isChildGridRecord(rowData)) {
883
            const cachedData = this.childGridTemplates.get(rowData.rowID);
884
            if (cachedData) {
885
                const view = cachedData.view;
886
                const tmlpOutlet = cachedData.owner;
887
                return {
888
                    $implicit: rowData,
889
                    moveView: view,
890
                    owner: tmlpOutlet,
891
                    index: this.dataView.indexOf(rowData)
892
                };
893
            } else {
894
                // child rows contain unique grids, hence should have unique templates
895
                return {
896
                    $implicit: rowData,
897
                    templateID: {
898
                        type: 'childRow',
899
                        id: rowData.rowID
900
                    },
901
                    index: this.dataView.indexOf(rowData)
902
                };
903
            }
904
        } else {
905
            return {
906
                $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData,
907
                templateID: {
908
                    type: 'dataRow',
909
                    id: null
910
                },
911
                index: this.getDataViewIndex(rowIndex, pinned),
912
                disabled: this.isGhostRecord(rowData)
913
            };
914
        }
915
    }
916

917
    /**
918
     * @hidden
919
     */
920
    public get rootGrid(): GridType {
921
        let currGrid = this as IgxHierarchicalGridComponent;
922
        while (currGrid.parent) {
923
            currGrid = currGrid.parent;
924
        }
925
        return currGrid;
926
    }
927

928
    /**
929
     * @hidden
930
     */
931
    public get iconTemplate() {
932
        const expanded = this.hasExpandedRecords() && this.hasExpandableChildren;
933
        if (!expanded && this.showExpandAll) {
934
            return this.headerCollapsedIndicatorTemplate || this.defaultCollapsedTemplate;
935
        } else {
936
            return this.headerExpandedIndicatorTemplate || this.defaultExpandedTemplate;
937
        }
938
    }
939

940
    /**
941
     * @hidden
942
     * @internal
943
     */
944
    public override getDragGhostCustomTemplate(): TemplateRef<any> {
945
        if (this.parentIsland) {
946
            return this.parentIsland.getDragGhostCustomTemplate();
947
        }
948
        return super.getDragGhostCustomTemplate();
949
    }
950

951
    /**
952
     * @hidden
953
     * Gets the visible content height that includes header + tbody + footer.
954
     * For hierarchical child grid it may be scrolled and not fully visible.
955
     */
956
    public override getVisibleContentHeight() {
957
        let height = super.getVisibleContentHeight();
958
        if (this.parent) {
959
            const rootHeight = this.rootGrid.getVisibleContentHeight();
960
            const topDiff = this.nativeElement.getBoundingClientRect().top - this.rootGrid.nativeElement.getBoundingClientRect().top;
961
            height = rootHeight - topDiff > height ? height : rootHeight - topDiff;
962
        }
963
        return height;
964
    }
965

966
    /**
967
     * @hidden
968
     */
969
    public toggleAll() {
970
        const expanded = this.hasExpandedRecords() && this.hasExpandableChildren;
971
        if (!expanded && this.showExpandAll) {
972
            this.expandAll();
973
        } else {
974
            this.collapseAll();
975
        }
976
    }
977

978

979
    /**
980
     * @hidden
981
     * @internal
982
     */
983
    public hasExpandedRecords() {
984
        if (this.expandChildren) {
985
            return true;
986
        }
987
        let hasExpandedEntry = false;
988
        this.expansionStates.forEach(value => {
989
            if (value) {
990
                hasExpandedEntry = value;
991
            }
992
        });
993
        return hasExpandedEntry;
994
    }
995

996
    public override getDefaultExpandState(record: any) {
997
        if (this.hasChildrenKey && !record[this.hasChildrenKey]) {
998
            return false;
999
        }
1000
        return this.expandChildren;
1001

1002
    }
1003

1004
    /**
1005
     * @hidden
1006
     */
1007
    public isExpanded(record: any): boolean {
1008
        return this.gridAPI.get_row_expansion_state(record);
1009
    }
1010

1011
    /**
1012
     * @hidden
1013
     */
1014
    public viewCreatedHandler(args) {
1015
        if (this.isChildGridRecord(args.context.$implicit)) {
1016
            const key = args.context.$implicit.rowID;
1017
            this.childGridTemplates.set(key, args);
1018
        }
1019
    }
1020

1021
    /**
1022
     * @hidden
1023
     */
1024
    public viewMovedHandler(args) {
1025
        if (this.isChildGridRecord(args.context.$implicit)) {
1026
            // view was moved, update owner in cache
1027
            const key = args.context.$implicit.rowID;
1028
            const cachedData = this.childGridTemplates.get(key);
1029
            cachedData.owner = args.owner;
1030

1031
            this.childLayoutList.forEach((layout) => {
1032
                const relatedGrid = this.gridAPI.getChildGridByID(layout.key, args.context.$implicit.rowID);
1033
                if (relatedGrid && relatedGrid.updateOnRender) {
1034
                    // Detect changes if `expandChildren` has changed when the grid wasn't visible. This is for performance reasons.
1035
                    relatedGrid.notifyChanges(true);
1036
                    relatedGrid.updateOnRender = false;
1037
                }
1038
            });
1039
        }
1040
    }
1041

1042
    /** @hidden @internal **/
1043
    public onContainerScroll() {
1044
        this.hideOverlays();
1045
    }
1046

1047
    /**
1048
     * @hidden
1049
     */
1050
    public createRow(index: number, data?: any): RowType {
1051
        let row: RowType;
1052
        const dataIndex = this._getDataViewIndex(index);
1053
        const rec: any = data ?? this.dataView[dataIndex];
1054

1055
        if (!row && rec && !rec.childGridsData) {
1056
            row = new IgxHierarchicalGridRow(this as any, index, rec);
1057
        }
1058

1059
        return row;
1060
    }
1061

1062
    /** @hidden @internal */
1063
    public getChildGrids(inDeph?: boolean) {
1064
        return this.gridAPI.getChildGrids(inDeph);
1065
    }
1066

1067
    protected override generateDataFields(data: any[]): string[] {
1068
        return super.generateDataFields(data).filter((field) => {
1069
            const layoutsList = this.parentIsland ? this.parentIsland.children : this.childLayoutList;
1070
            const keys = layoutsList.map((item) => item.key);
1071
            return keys.indexOf(field) === -1;
1072
        });
1073
    }
1074

1075
    protected resizeNotifyHandler() {
1076
        // do not trigger reflow if element is detached or if it is child grid.
1077
        if (this.document.contains(this.nativeElement) && !this.parent) {
1078
            this.notifyChanges(true);
1079
        }
1080
    }
1081

1082
    /**
1083
     * @hidden
1084
     */
1085
    protected override initColumns(collection: IgxColumnComponent[], cb: (args: any) => void = null) {
1086
        if (this.hasColumnLayouts) {
1087
            // invalid configuration - hierarchical grid should not allow column layouts
1088
            // remove column layouts
1089
            const nonColumnLayoutColumns = this.columns.filter((col) => !col.columnLayout && !col.columnLayoutChild);
1090
            this.updateColumns(nonColumnLayoutColumns);
1091
        }
1092
        super.initColumns(collection, cb);
1093
    }
1094

1095

1096
    protected override setupColumns() {
1097
        if (this.parentIsland && this.parentIsland.childColumns.length > 0 && !this.autoGenerate) {
1098
            this.createColumnsList(this.parentIsland.childColumns.toArray());
1099
        } else {
1100
            super.setupColumns();
1101
        }
1102
    }
1103

1104
    protected override getColumnList() {
1105
        const childLayouts = this.parent ? this.childLayoutList : this.allLayoutList;
1106
        const nestedColumns = childLayouts.map((layout) => layout.columnList.toArray());
1107
        const colsArray = [].concat.apply([], nestedColumns);
1108
        if (colsArray.length > 0) {
1109
            const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
1110
            return topCols;
1111
        } else {
1112
           return this.columnList.toArray()
1113
        }
1114
    }
1115

1116
    protected override onColumnsChanged() {
1117
        Promise.resolve().then(() => {
1118
            this.updateColumnList();
1119
        });
1120
    }
1121

1122
    protected override _shouldAutoSize(renderedHeight) {
1123
        if (this.isPercentHeight && this.parent) {
1124
            return true;
1125
        }
1126
        return super._shouldAutoSize(renderedHeight);
1127
    }
1128

1129
    private updateColumnList(recalcColSizes = true) {
1130
        const childLayouts = this.parent ? this.childLayoutList : this.allLayoutList;
1131
        const nestedColumns = childLayouts.map((layout) => layout.columnList.toArray());
1132
        const colsArray = [].concat.apply([], nestedColumns);
1133
        const colLength = this.columns.length;
1134
        const topCols = this.columnList.filter((item) => colsArray.indexOf(item) === -1);
1135
        if (topCols.length > 0) {
1136
            this.updateColumns(topCols);
1137
            if (recalcColSizes && this.columns.length !== colLength) {
1138
                this.calculateGridSizes(false);
1139
            }
1140
        }
1141
    }
1142

1143
    private _clearSeletionHighlights() {
1144
        [this.rootGrid, ...this.rootGrid.getChildGrids(true)].forEach(grid => {
1145
            grid.selectionService.clear();
1146
            grid.selectionService.activeElement = null;
1147
            grid.nativeElement.classList.remove('igx-grid__tr--highlighted');
1148
            grid.highlightedRowID = null;
1149
            grid.cdr.markForCheck();
1150
        });
1151
    }
1152
}
1153

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