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

IgniteUI / igniteui-angular / 29572405564

17 Jul 2026 10:07AM UTC coverage: 90.116% (-0.02%) from 90.135%
29572405564

Pull #15125

github

web-flow
Merge 637b7229e into 8ebabbb38
Pull Request #15125: refactor(*): bundle styles with components

14920 of 17397 branches covered (85.76%)

Branch coverage included in aggregate %.

30030 of 32483 relevant lines covered (92.45%)

34495.14 hits per line

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

94.75
/projects/igniteui-angular/grids/grid/src/grid.component.ts
1
import {
2
    Component, ChangeDetectionStrategy, Input, Output, EventEmitter, ContentChild, ViewChildren,
3
    QueryList, ViewChild, TemplateRef, DoCheck, AfterContentInit, HostBinding,
4
    OnInit, AfterViewInit, ContentChildren, CUSTOM_ELEMENTS_SCHEMA, booleanAttribute, ViewEncapsulation,
5
} from '@angular/core';
6
import { NgTemplateOutlet, NgClass, NgStyle } from '@angular/common';
7
import {
8
    CellType,
9
    FilterMode,
10
    GridType,
11
    IGX_GRID_BASE,
12
    IGX_GRID_SERVICE_BASE,
13
    IgxColumnComponent,
14
    IgxColumnMovingDropDirective,
15
    IgxColumnResizingService,
16
    IgxFilteringService,
17
    IgxGridAddRowPipe,
18
    IgxGridBodyDirective,
19
    IgxGridCell,
20
    IgxGridColumnResizerComponent,
21
    IgxGridCRUDService,
22
    IgxGridDetailTemplateDirective,
23
    IgxGridDragSelectDirective,
24
    IgxGridHeaderRowComponent,
25
    IgxGridMasterDetailContext,
26
    IgxGridMRLNavigationService,
27
    IgxGridNavigationService,
28
    IgxGridRow,
29
    IgxGridRowClassesPipe,
30
    IgxGridRowPinningPipe,
31
    IgxGridRowStylesPipe,
32
    IgxGridSelectionService,
33
    IgxGridSummaryService,
34
    IgxGridTransactionPipe,
35
    IgxGridValidationService,
36
    IgxGroupByRow,
37
    IgxGroupByRowSelectorDirective,
38
    IgxGroupByRowSelectorTemplateContext,
39
    IgxGroupByRowTemplateContext,
40
    IgxGroupByRowTemplateDirective,
41
    IgxHasVisibleColumnsPipe,
42
    IgxRowEditTabStopDirective,
43
    IgxStringReplacePipe,
44
    IgxSummaryDataPipe,
45
    IgxSummaryRow,
46
    IgxSummaryRowComponent,
47
    RowType
48
} from 'igniteui-angular/grids/core';
49
import { IgxGridAPIService } from './grid-api.service';
50
import { IgxGridGroupByRowComponent } from './groupby-row.component';
51
import { IgxGridGroupByAreaComponent } from './grouping/grid-group-by-area.component';
52
import { take, takeUntil } from 'rxjs/operators';
53
import { cloneArray, IBaseEventArgs, IGridGroupingStrategy, IGroupByExpandState, IGroupByRecord, IGroupingExpression, IgxOverlayOutletDirective, ISortingExpression } from 'igniteui-angular/core';
54
import { IgxGridDetailsPipe } from './grid.details.pipe';
55
import { IgxGridSummaryPipe } from './grid.summary.pipe';
56
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe, IgxGridCellMergePipe, IgxGridUnmergeActivePipe } from './grid.pipes';
57
import { IgxGridRowComponent } from './grid-row.component';
58
import { Observable, Subject } from 'rxjs';
59
import { IForOfState, IgxButtonDirective, IgxForOfScrollSyncService, IgxForOfSyncService, IgxGridForOfDirective, IgxRippleDirective, IgxScrollInertiaDirective, IgxTemplateOutletDirective, IgxToggleDirective } from 'igniteui-angular/directives';
60
import { IgxCircularProgressBarComponent } from 'igniteui-angular/progressbar';
61
import { IgxSnackbarComponent } from 'igniteui-angular/snackbar';
62
import { IgxIconComponent } from 'igniteui-angular/icon';
63
import { IgxGridBaseDirective } from './grid-base.directive';
64

65
let NEXT_ID = 0;
3✔
66

67
export interface IGroupingDoneEventArgs extends IBaseEventArgs {
68
    expressions: Array<ISortingExpression> | ISortingExpression;
69
    groupedColumns: Array<IgxColumnComponent> | IgxColumnComponent;
70
    ungroupedColumns: Array<IgxColumnComponent> | IgxColumnComponent;
71
}
72

73
/* wcAlternateName: GridBase */
74
/* blazorIndirectRender
75
   blazorComponent
76
   omitModule
77
   wcSkipComponentSuffix */
78
/**
79
 * Grid provides a way to present and manipulate tabular data.
80
 *
81
 * @igxModule IgxGridModule
82
 * @igxGroup Grids & Lists
83
 * @igxKeywords grid, table
84
 * @igxTheme igx-grid-theme
85
 * @remarks
86
 * The Ignite UI Grid is used for presenting and manipulating tabular data in the simplest way possible.  Once data
87
 * has been bound, it can be manipulated through filtering, sorting & editing operations.
88
 * @example
89
 * ```html
90
 * <igx-grid [data]="employeeData" [autoGenerate]="false">
91
 *   <igx-column field="first" header="First Name"></igx-column>
92
 *   <igx-column field="last" header="Last Name"></igx-column>
93
 *   <igx-column field="role" header="Role"></igx-column>
94
 * </igx-grid>
95
 * ```
96
 */
97
@Component({
98
    changeDetection: ChangeDetectionStrategy.OnPush,
99
    preserveWhitespaces: false,
100
    providers: [
101
        IgxGridCRUDService,
102
        IgxGridMRLNavigationService,
103
        IgxGridNavigationService,
104
        IgxGridSummaryService,
105
        IgxGridSelectionService,
106
        IgxGridValidationService,
107
        { provide: IGX_GRID_SERVICE_BASE, useClass: IgxGridAPIService },
108
        { provide: IGX_GRID_BASE, useExisting: IgxGridComponent },
109
        IgxFilteringService,
110
        IgxColumnResizingService,
111
        IgxForOfSyncService,
112
        IgxForOfScrollSyncService,
113
    ],
114
    selector: 'igx-grid',
115
    templateUrl: './grid.component.html',
116
    encapsulation: ViewEncapsulation.None,
117
    imports: [
118
        NgClass,
119
        NgStyle,
120
        NgTemplateOutlet,
121
        IgxGridGroupByAreaComponent,
122
        IgxGridHeaderRowComponent,
123
        IgxGridBodyDirective,
124
        IgxGridDragSelectDirective,
125
        IgxColumnMovingDropDirective,
126
        IgxGridForOfDirective,
127
        IgxTemplateOutletDirective,
128
        IgxGridRowComponent,
129
        IgxGridGroupByRowComponent,
130
        IgxSummaryRowComponent,
131
        IgxOverlayOutletDirective,
132
        IgxToggleDirective,
133
        IgxCircularProgressBarComponent,
134
        IgxSnackbarComponent,
135
        IgxButtonDirective,
136
        IgxRippleDirective,
137
        IgxIconComponent,
138
        IgxRowEditTabStopDirective,
139
        IgxGridColumnResizerComponent,
140
        IgxGridTransactionPipe,
141
        IgxHasVisibleColumnsPipe,
142
        IgxGridRowPinningPipe,
143
        IgxGridAddRowPipe,
144
        IgxGridRowClassesPipe,
145
        IgxGridRowStylesPipe,
146
        IgxSummaryDataPipe,
147
        IgxGridGroupingPipe,
148
        IgxGridPagingPipe,
149
        IgxGridSortingPipe,
150
        IgxGridFilteringPipe,
151
        IgxGridSummaryPipe,
152
        IgxGridDetailsPipe,
153
        IgxStringReplacePipe,
154
        IgxGridCellMergePipe,
155
        IgxGridUnmergeActivePipe,
156
        IgxScrollInertiaDirective
157
    ],
158
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
159
})
160
export class IgxGridComponent extends IgxGridBaseDirective implements GridType, OnInit, DoCheck, AfterContentInit, AfterViewInit {
3✔
161
    /**
162
     * Emitted when a new chunk of data is loaded from virtualization.
163
     *
164
     * @example
165
     * ```typescript
166
     *  <igx-grid #grid [data]="localData" [autoGenerate]="true" (dataPreLoad)='handleDataPreloadEvent()'></igx-grid>
167
     * ```
168
     */
169
    @Output()
170
    public dataPreLoad = new EventEmitter<IForOfState>();
2,130✔
171

172
    /**
173
     * Emitted when grouping is performed.
174
     *
175
     * @example
176
     * ```html
177
     * <igx-grid #grid [data]="localData" [autoGenerate]="true" (groupingExpressionsChange)="groupingExpressionsChange($event)"></igx-grid>
178
     * ```
179
     */
180
    @Output()
181
    public groupingExpressionsChange = new EventEmitter<IGroupingExpression[]>();
2,130✔
182

183
    /**
184
     * Emitted when groups are expanded/collapsed.
185
     *
186
     * @example
187
     * ```html
188
     * <igx-grid #grid [data]="localData" [autoGenerate]="true" (groupingExpansionStateChange)="groupingExpansionStateChange($event)"></igx-grid>
189
     * ```
190
     */
191
    @Output()
192
    public groupingExpansionStateChange = new EventEmitter<IGroupByExpandState[]>();
2,130✔
193

194
    /**
195
     * Emitted when columns are grouped/ungrouped.
196
     *
197
     * @remarks
198
     * The `groupingDone` event would be raised only once if several columns get grouped at once by calling
199
     * the `groupBy()` or `clearGrouping()` API methods and passing an array as an argument.
200
     * The event arguments provide the `expressions`, `groupedColumns` and `ungroupedColumns` properties, which contain
201
     * the `ISortingExpression` and the column related to the grouping/ungrouping operation.
202
     * Please note that `groupedColumns` and `ungroupedColumns` show only the **newly** changed columns (affected by the **last**
203
     * grouping/ungrouping operation), not all columns which are currently grouped/ungrouped.
204
     * columns.
205
     * @example
206
     * ```html
207
     * <igx-grid #grid [data]="localData" (groupingDone)="groupingDone($event)" [autoGenerate]="true"></igx-grid>
208
     * ```
209
     */
210
    @Output()
211
    public groupingDone = new EventEmitter<IGroupingDoneEventArgs>();
2,130✔
212

213
    /**
214
     * Gets/Sets whether created groups are rendered expanded or collapsed.
215
     *
216
     * @remarks
217
     * The default rendered state is expanded.
218
     * @example
219
     * ```html
220
     * <igx-grid #grid [data]="Data" [groupsExpanded]="false" [autoGenerate]="true"></igx-grid>
221
     * ```
222
     */
223
    @Input({ transform: booleanAttribute })
224
    public groupsExpanded = true;
2,130✔
225

226
    /**
227
     * Gets/Sets the template that will be rendered as a GroupBy drop area.
228
     *
229
     * @remarks
230
     * The grid needs to have at least one groupable column in order the GroupBy area to be displayed.
231
     * @example
232
     * ```html
233
     * <igx-grid [dropAreaTemplate]="dropAreaRef">
234
     * </igx-grid>
235
     * <ng-template #myDropArea>
236
     *      <span> Custom drop area! </span>
237
     * </ng-template>
238
     * ```
239
     */
240
    @Input()
241
    public dropAreaTemplate: TemplateRef<void>;
242

243
    /**
244
     * @hidden @internal
245
     */
246
    @ContentChild(IgxGridDetailTemplateDirective, { read: TemplateRef })
247
    public detailTemplateDirective: TemplateRef<IgxGridMasterDetailContext>;
248

249

250
    /**
251
     * Returns a reference to the master-detail template.
252
     * ```typescript
253
     * let detailTemplate = this.grid.detailTemplate;
254
     * ```
255
     *
256
     * @memberof IgxColumnComponent
257
     */
258
    @Input('detailTemplate')
259
    public get detailTemplate(): TemplateRef<IgxGridMasterDetailContext> {
260
        return this._detailTemplate;
314,392✔
261
    }
262
    /**
263
     * Sets the master-detail template.
264
     * ```html
265
     * <ng-template #detailTemplate igxGridDetail let-dataItem>
266
     *    <div>
267
     *       <div><span class='categoryStyle'>City:</span> {{dataItem.City}}</div>
268
     *       <div><span class='categoryStyle'>Address:</span> {{dataItem.Address}}</div>
269
     *    </div>
270
     * </ng-template>
271
     * ```
272
     * ```typescript
273
     * @ViewChild("'detailTemplate'", {read: TemplateRef })
274
     * public detailTemplate: TemplateRef<any>;
275
     * this.grid.detailTemplate = this.detailTemplate;
276
     * ```
277
     *
278
     * @memberof IgxColumnComponent
279
     */
280
    public set detailTemplate(template: TemplateRef<IgxGridMasterDetailContext>) {
281
        this._detailTemplate = template;
3✔
282
    }
283

284
    /**
285
     * @hidden @internal
286
     */
287
    @HostBinding('attr.role')
288
    public role = 'grid';
2,130✔
289

290
    /**
291
     * Gets/Sets the value of the `id` attribute.
292
     *
293
     * @remarks
294
     * If not provided it will be automatically generated.
295
     * @example
296
     * ```html
297
     * <igx-grid [id]="'igx-grid-1'" [data]="Data" [autoGenerate]="true"></igx-grid>
298
     * ```
299
     */
300
    @HostBinding('attr.id')
301
    @Input()
302
    public id = `igx-grid-${NEXT_ID++}`;
2,130✔
303

304
    /**
305
     * @hidden @internal
306
     */
307
    @ViewChild('record_template', { read: TemplateRef, static: true })
308
    protected recordTemplate: TemplateRef<any>;
309

310
    @ViewChild('detail_template_container', { read: TemplateRef, static: true })
311
    protected detailTemplateContainer: TemplateRef<any>;
312

313
    @ViewChild('group_template', { read: TemplateRef, static: true })
314
    protected defaultGroupTemplate: TemplateRef<any>;
315

316
    @ViewChild('summary_template', { read: TemplateRef, static: true })
317
    protected summaryTemplate: TemplateRef<any>;
318

319
    /**
320
     * @hidden @internal
321
     */
322
    @ContentChild(IgxGroupByRowTemplateDirective, { read: IgxGroupByRowTemplateDirective })
323
    protected groupTemplate: IgxGroupByRowTemplateDirective;
324

325
    /**
326
     * @hidden
327
     * @internal
328
     */
329
    @ContentChildren(IgxGroupByRowSelectorDirective, { read: TemplateRef, descendants: false })
330
    protected groupByRowSelectorsTemplates: QueryList<TemplateRef<IgxGroupByRowSelectorTemplateContext>>;
331

332
    @ViewChildren(IgxGridGroupByRowComponent, { read: IgxGridGroupByRowComponent })
333
    private _groupsRowList: QueryList<IgxGridGroupByRowComponent>;
334

335
    private _groupsRecords: IGroupByRecord[] = [];
2,130✔
336
    /**
337
     * Gets the hierarchical representation of the group by records.
338
     *
339
     * @example
340
     * ```typescript
341
     * let groupRecords = this.grid.groupsRecords;
342
     * ```
343
     */
344
    public get groupsRecords(): IGroupByRecord[] {
345
        return this._groupsRecords;
26,293✔
346
    }
347

348
    /**
349
     * @hidden @internal
350
     * Includes children of collapsed group rows.
351
     */
352
    public groupingResult: any[];
353

354
    /**
355
     * @hidden @internal
356
     */
357
    public groupingMetadata: any[];
358

359
    /**
360
     * @hidden @internal
361
     * Does not include children of collapsed group rows.
362
     */
363
    public groupingFlatResult: any[];
364
    /**
365
     * @hidden
366
     */
367
    protected _groupingExpressions: IGroupingExpression[] = [];
2,130✔
368
    /**
369
     * @hidden
370
     */
371
    protected _groupingExpandState: IGroupByExpandState[] = [];
2,130✔
372
    /**
373
     * @hidden
374
     */
375
    protected _groupRowTemplate: TemplateRef<IgxGroupByRowTemplateContext>;
376

377
    /**
378
     * @hidden
379
     */
380
    protected _groupStrategy: IGridGroupingStrategy;
381
    /**
382
     * @hidden
383
     */
384
    protected groupingDiffer;
385
    private _data?: any[] | null;
386
    private _hideGroupedColumns = false;
2,130✔
387
    private _dropAreaMessage = null;
2,130✔
388
    private _showGroupArea = true;
2,130✔
389

390
    private _groupByRowSelectorTemplate: TemplateRef<IgxGroupByRowSelectorTemplateContext>;
391
    private _detailTemplate;
392

393
    /**
394
     * Gets/Sets the array of data that populates the component.
395
     *
396
     * @example
397
     * ```html
398
     * <igx-grid [data]="Data" [autoGenerate]="true"></igx-grid>
399
     * ```
400
     */
401
    /* treatAsRef */
402
    @Input()
403
    public get data(): any[] | null {
404
        return this._data;
130,696✔
405
    }
406

407
    public set data(value: any[] | null) {
408
        const dataLoaded = (!this._data || this._data.length === 0) && value && value.length > 0;
2,218✔
409
        const oldData = this._data;
2,218✔
410
        this._data = value || [];
2,218✔
411
        this.summaryService.clearSummaryCache();
2,218✔
412
        if (!this._init) {
2,218✔
413
            this.validation.updateAll(this._data);
105✔
414
        }
415

416
        if (this.autoGenerate && this._data.length > 0 && this.shouldRecreateColumns(oldData, this._data) && this.gridAPI.grid) {
2,218✔
417
            this.setupColumns();
21✔
418
        }
419

420
        this.cdr.markForCheck();
2,218✔
421
        if (this.isPercentHeight) {
2,218✔
422
            this.notifyChanges(true);
1,025✔
423
        }
424
        // check if any columns have width auto and if so recalculate their auto-size on data loaded.
425
        if (dataLoaded && this._columns.some(x => (x as any)._width === 'auto')) {
2,218✔
426
            this.recalculateAutoSizes();
1✔
427
        }
428
        this.checkPrimaryKeyField();
2,218✔
429
    }
430

431
    /**
432
     * Gets/Sets the total number of records in the data source.
433
     *
434
     * @remarks
435
     * This property is required for remote grid virtualization to function when it is bound to remote data.
436
     * @example
437
     * ```typescript
438
     * const itemCount = this.grid1.totalItemCount;
439
     * this.grid1.totalItemCount = 55;
440
     * ```
441
     */
442
    @Input()
443
    public set totalItemCount(count) {
444
        this.verticalScrollContainer.totalItemCount = count;
5✔
445
    }
446

447
    public get totalItemCount() {
448
        return this.verticalScrollContainer.totalItemCount;
1,259✔
449
    }
450

451
    private get _gridAPI(): IgxGridAPIService {
452
        return this.gridAPI as IgxGridAPIService;
7,668✔
453
    }
454

455
    private childDetailTemplates: Map<any, any> = new Map();
2,130✔
456

457
    /**
458
     * @hidden @internal
459
     */
460
    public groupingPerformedSubject = new Subject<void>();
2,130✔
461

462
    /**
463
     * @hidden @internal
464
     */
465
    public groupingPerformed$: Observable<void> = this.groupingPerformedSubject.asObservable();
2,130✔
466

467
    /* mustSetInCodePlatforms: WebComponents;Blazor;React */
468
    /**
469
     * Gets/Sets the group by state.
470
     *
471
     * @example
472
     * ```typescript
473
     * let groupByState = this.grid.groupingExpressions;
474
     * this.grid.groupingExpressions = [...];
475
     * ```
476
     * @remarks
477
     * Supports two-way data binding.
478
     * @example
479
     * ```html
480
     * <igx-grid #grid [data]="Data" [autoGenerate]="true" [(groupingExpressions)]="model.groupingExpressions"></igx-grid>
481
     * ```
482
     */
483
    @Input()
484
    public get groupingExpressions(): IGroupingExpression[] {
485
        return this._groupingExpressions;
1,872,564✔
486
    }
487

488
    public set groupingExpressions(value: IGroupingExpression[]) {
489
        if (this.groupingExpressions === value) {
629✔
490
            return;
116✔
491
        }
492
        if (value && value.length > 10) {
513✔
493
            throw Error('Maximum amount of grouped columns is 10.');
1✔
494
        }
495
        const oldExpressions: IGroupingExpression[] = this.groupingExpressions;
512✔
496
        const newExpressions: IGroupingExpression[] = value;
512✔
497
        this._groupingExpressions = cloneArray(value);
512✔
498
        this.groupingExpressionsChange.emit(this._groupingExpressions);
512✔
499
        if (this._gridAPI.grid) {
512✔
500
            /* grouping and sorting are working separate from each other */
501
            this._applyGrouping();
412✔
502
            this.notifyChanges();
412✔
503
        }
504
        if (!this._init && JSON.stringify(oldExpressions, this.stringifyCallback) !== JSON.stringify(newExpressions, this.stringifyCallback) && this._columns) {
512✔
505
            const groupedCols: IgxColumnComponent[] = [];
279✔
506
            const ungroupedCols: IgxColumnComponent[] = [];
279✔
507
            const groupedColsArr = newExpressions.filter((obj) => !oldExpressions.some((obj2) => obj.fieldName === obj2.fieldName));
334✔
508
            groupedColsArr.forEach((elem) => {
279✔
509
                groupedCols.push(this.getColumnByName(elem.fieldName));
278✔
510
            }, this);
511
            const ungroupedColsArr = oldExpressions.filter((obj) => !newExpressions.some((obj2) => obj.fieldName === obj2.fieldName));
279✔
512
            ungroupedColsArr.forEach((elem) => {
279✔
513
                ungroupedCols.push(this.getColumnByName(elem.fieldName));
26✔
514
            }, this);
515
            this.notifyChanges();
279✔
516
            const groupingDoneArgs: IGroupingDoneEventArgs = {
279✔
517
                expressions: newExpressions,
518
                groupedColumns: groupedCols,
519
                ungroupedColumns: ungroupedCols
520
            };
521
            this.groupingPerformed$.pipe(take(1)).subscribe(() => {
279✔
522
                this.groupingDone.emit(groupingDoneArgs);
274✔
523
            });
524
        }
525
    }
526

527
    /**
528
     * Gets/Sets a list of expansion states for group rows.
529
     *
530
     * @remarks
531
     * Includes only states that differ from the default one (controlled through groupsExpanded and states that the user has changed.
532
     * Contains the expansion state (expanded: boolean) and the unique identifier for the group row (Array).
533
     * Supports two-way data binding.
534
     * @example
535
     * ```html
536
     * <igx-grid #grid [data]="Data" [autoGenerate]="true" [(groupingExpansionState)]="model.groupingExpansionState"></igx-grid>
537
     * ```
538
     */
539
    @Input()
540
    public get groupingExpansionState() {
541
        return this._groupingExpandState;
33,561✔
542
    }
543

544
    public set groupingExpansionState(value) {
545
        if (value !== this._groupingExpandState) {
99✔
546
            this.groupingExpansionStateChange.emit(value);
98✔
547
        }
548
        this._groupingExpandState = value;
99✔
549
        if (this.gridAPI.grid) {
99✔
550
            this.cdr.detectChanges();
99✔
551
        }
552
    }
553

554
    /**
555
     * Gets/Sets whether the grouped columns should be hidden.
556
     *
557
     * @remarks
558
     * The default value is "false"
559
     * @example
560
     * ```html
561
     * <igx-grid #grid [data]="localData" [hideGroupedColumns]="true" [autoGenerate]="true"></igx-grid>
562
     * ```
563
     */
564
    @Input({ transform: booleanAttribute })
565
    public get hideGroupedColumns() {
566
        return this._hideGroupedColumns;
2,213✔
567
    }
568

569
    public set hideGroupedColumns(value: boolean) {
570
        if (value) {
37✔
571
            this.groupingDiffer = this.differs.find(this.groupingExpressions).create();
16✔
572
        } else {
573
            this.groupingDiffer = null;
21✔
574
        }
575
        if (this._columns && this.groupingExpressions) {
37✔
576
            this._setGroupColsVisibility(value);
37✔
577
        }
578

579
        this._hideGroupedColumns = value;
37✔
580
    }
581

582
    /**
583
     * Gets/Sets the grouping strategy of the grid.
584
     *
585
     * @remarks The default grouping extends from sorting and a custom one can be used as a `sortStrategy` as well.
586
     *
587
     * @example
588
     * ```html
589
     *  <igx-grid #grid [data]="localData" [groupStrategy]="groupStrategy"></igx-grid>
590
     * ```
591
     */
592
    @Input()
593
    public get groupStrategy(): IGridGroupingStrategy {
594
        return this._groupStrategy;
26,270✔
595
    }
596

597
    public set groupStrategy(value: IGridGroupingStrategy) {
598
        this._groupStrategy = value;
14✔
599
    }
600

601
    /**
602
     * Gets/Sets the message displayed inside the GroupBy drop area where columns can be dragged on.
603
     *
604
     * @remarks
605
     * The grid needs to have at least one groupable column in order the GroupBy area to be displayed.
606
     * @example
607
     * ```html
608
     * <igx-grid dropAreaMessage="Drop here to group!">
609
     *      <igx-column [groupable]="true" field="ID"></igx-column>
610
     * </igx-grid>
611
     * ```
612
     */
613
    @Input()
614
    public set dropAreaMessage(value: string) {
615
        this._dropAreaMessage = value;
1✔
616
        this.notifyChanges();
1✔
617
    }
618

619
    public get dropAreaMessage(): string {
620
        return this._dropAreaMessage || this.resourceStrings.igx_grid_groupByArea_message;
3,853✔
621
    }
622

623
    /**
624
     * @hidden @internal
625
     */
626
    public get groupsRowList() {
627
        const res = new QueryList<any>();
150✔
628
        if (!this._groupsRowList) {
150!
629
            return res;
×
630
        }
631
        const rList = this._groupsRowList.filter(item => item.element.nativeElement.parentElement !== null)
637✔
632
            .sort((item1, item2) => item1.index - item2.index);
786✔
633
        res.reset(rList);
150✔
634
        return res;
150✔
635
    }
636

637
    /**
638
     * Gets the group by row selector template.
639
     */
640
    @Input()
641
    public get groupByRowSelectorTemplate(): TemplateRef<IgxGroupByRowSelectorTemplateContext> {
642
        return this._groupByRowSelectorTemplate || this.groupByRowSelectorsTemplates?.first;
399✔
643
    }
644

645
    /**
646
     * Sets the group by row selector template.
647
     * ```html
648
     * <ng-template #template igxGroupByRowSelector let-groupByRowContext>
649
     * {{ groupByRowContext.selectedCount }} / {{ groupByRowContext.totalCount  }}
650
     * </ng-template>
651
     * ```
652
     * ```typescript
653
     * @ViewChild("'template'", {read: TemplateRef })
654
     * public template: TemplateRef<any>;
655
     * this.grid.groupByRowSelectorTemplate = this.template;
656
     * ```
657
     */
658
    public set groupByRowSelectorTemplate(template: TemplateRef<IgxGroupByRowSelectorTemplateContext>) {
659
        this._groupByRowSelectorTemplate = template;
1✔
660
    }
661

662
    /**
663
     * @hidden @internal
664
     */
665
    public getDetailsContext(rowData, index): IgxGridDetailTemplateDirective {
666
        return {
2,366✔
667
            $implicit: rowData,
668
            index
669
        };
670
    }
671

672
    /**
673
     * @hidden @internal
674
     */
675
    public detailsViewFocused(_container, rowIndex) {
676
        this.navigation.setActiveNode({ row: rowIndex });
2✔
677
    }
678

679
    /**
680
     * @hidden @internal
681
     */
682
    public override get hasDetails() {
683
        return !!this.detailTemplate;
312,026✔
684
    }
685

686
    /**
687
     * @hidden @internal
688
     */
689
    public getRowTemplate(rowData) {
690
        if (this.isGroupByRecord(rowData)) {
152,153✔
691
            return this.defaultGroupTemplate;
8,693✔
692
        } else if (this.isSummaryRow(rowData)) {
143,460✔
693
            return this.summaryTemplate;
1,547✔
694
        } else if (this.hasDetails && this.isDetailRecord(rowData)) {
141,913✔
695
            return this.detailTemplateContainer;
2,366✔
696
        } else {
697
            return this.recordTemplate;
139,547✔
698
        }
699
    }
700

701
    /**
702
     * @hidden @internal
703
     */
704
    public override isDetailRecord(record) {
705
        return record && record.detailsData !== undefined;
167,443✔
706
    }
707

708
    /**
709
     * @hidden @internal
710
     */
711
    public isDetailActive(rowIndex) {
712
        return this.navigation.activeNode ? this.navigation.activeNode.row === rowIndex : false;
2,366!
713
    }
714

715
    /**
716
     * Gets/Sets the template reference for the group row.
717
     *
718
     * @example
719
     * ```
720
     * const groupRowTemplate = this.grid.groupRowTemplate;
721
     * this.grid.groupRowTemplate = myRowTemplate;
722
     * ```
723
     */
724
    @Input()
725
    public get groupRowTemplate(): TemplateRef<IgxGroupByRowTemplateContext> {
726
        return this._groupRowTemplate;
2,287✔
727
    }
728

729
    public set groupRowTemplate(template: TemplateRef<IgxGroupByRowTemplateContext>) {
730
        this._groupRowTemplate = template;
1✔
731
        this.notifyChanges();
1✔
732
    }
733

734
    /** @hidden @internal */
735
    public trackChanges: (index, rec) => any;
736

737
    /**
738
     * Groups by a new column based on the provided expression, or modifies an existing one.
739
     *
740
     * @remarks
741
     * Also allows for multiple columns to be grouped at once if an array of `ISortingExpression` is passed.
742
     * The `groupingDone` event would get raised only **once** if this method gets called multiple times with the same arguments.
743
     * @example
744
     * ```typescript
745
     * this.grid.groupBy({ fieldName: name, dir: SortingDirection.Asc, ignoreCase: false });
746
     * this.grid.groupBy([
747
     *     { fieldName: name1, dir: SortingDirection.Asc, ignoreCase: false },
748
     *     { fieldName: name2, dir: SortingDirection.Desc, ignoreCase: true },
749
     *     { fieldName: name3, dir: SortingDirection.Desc, ignoreCase: false }
750
     * ]);
751
     * ```
752
     */
753
    public groupBy(expression: IGroupingExpression | Array<IGroupingExpression>): void {
754
        if (this.checkIfNoColumnField(expression)) {
238✔
755
            return;
1✔
756
        }
757
        this.crudService.endEdit(false);
237✔
758
        if (expression instanceof Array) {
237✔
759
            this._gridAPI.groupBy_multiple(expression);
20✔
760
        } else {
761
            this._gridAPI.groupBy(expression);
217✔
762
        }
763
        this.notifyChanges(true);
236✔
764
    }
765

766
    /**
767
     * Clears grouping for particular column, array of columns or all columns.
768
     *
769
     * @remarks
770
     * Clears all grouping in the grid, if no parameter is passed.
771
     * If a parameter is provided, clears grouping for a particular column or an array of columns.
772
     * @example
773
     * ```typescript
774
     * this.grid.clearGrouping(); //clears all grouping
775
     * this.grid.clearGrouping("ID"); //ungroups a single column
776
     * this.grid.clearGrouping(["ID", "Column1", "Column2"]); //ungroups multiple columns
777
     * ```
778
     * @param name Name of column or array of column names to be ungrouped.
779
     */
780
    public clearGrouping(name?: string | Array<string>): void {
781
        this._gridAPI.clear_groupby(name);
18✔
782
        this.calculateGridSizes();
18✔
783
        this.notifyChanges(true);
18✔
784
        this.groupingPerformedSubject.next();
18✔
785
    }
786

787
    /**
788
     * Returns if a group is expanded or not.
789
     *
790
     * @param group The group record.
791
     * @example
792
     * ```typescript
793
     * public groupRow: IGroupByRecord;
794
     * const expandedGroup = this.grid.isExpandedGroup(this.groupRow);
795
     * ```
796
     */
797
    public override isExpandedGroup(group: IGroupByRecord): boolean {
798
        const state: IGroupByExpandState = this._getStateForGroupRow(group);
6,392✔
799
        return state ? state.expanded : this.groupsExpanded;
6,392✔
800
    }
801

802
    /**
803
     * Toggles the expansion state of a group.
804
     *
805
     * @param groupRow The group record to toggle.
806
     * @example
807
     * ```typescript
808
     * public groupRow: IGroupByRecord;
809
     * const toggleExpGroup = this.grid.toggleGroup(this.groupRow);
810
     * ```
811
     */
812
    public toggleGroup(groupRow: IGroupByRecord) {
813
        this._toggleGroup(groupRow);
60✔
814
        this.notifyChanges();
60✔
815
    }
816

817
    /**
818
     * Select all rows within a group.
819
     *
820
     * @param groupRow: The group record which rows would be selected.
821
     * @param clearCurrentSelection if true clears the current selection
822
     * @example
823
     * ```typescript
824
     * this.grid.selectRowsInGroup(this.groupRow, true);
825
     * ```
826
     */
827
    public selectRowsInGroup(groupRow: IGroupByRecord, clearPrevSelection?: boolean) {
828
        this._gridAPI.groupBy_select_all_rows_in_group(groupRow, clearPrevSelection);
4✔
829
        this.notifyChanges();
4✔
830
    }
831

832
    /**
833
     * Deselect all rows within a group.
834
     *
835
     * @param groupRow The group record which rows would be deselected.
836
     * @example
837
     * ```typescript
838
     * public groupRow: IGroupByRecord;
839
     * this.grid.deselectRowsInGroup(this.groupRow);
840
     * ```
841
     */
842
    public deselectRowsInGroup(groupRow: IGroupByRecord) {
843
        this._gridAPI.groupBy_deselect_all_rows_in_group(groupRow);
2✔
844
        this.notifyChanges();
2✔
845
    }
846

847
    /**
848
     * Expands the specified group and all of its parent groups.
849
     *
850
     * @param groupRow The group record to fully expand.
851
     * @example
852
     * ```typescript
853
     * public groupRow: IGroupByRecord;
854
     * this.grid.fullyExpandGroup(this.groupRow);
855
     * ```
856
     */
857
    public fullyExpandGroup(groupRow: IGroupByRecord) {
858
        this._fullyExpandGroup(groupRow);
×
859
        this.notifyChanges();
×
860
    }
861

862
    /**
863
     * @hidden @internal
864
     */
865
    public override isGroupByRecord(record: any): boolean {
866
        // return record.records instance of GroupedRecords fails under Webpack
867
        return record && record?.records && record.records?.length &&
369,060✔
868
            record.expression && record.expression?.fieldName;
869
    }
870

871
    /**
872
     * Toggles the expansion state of all group rows recursively.
873
     *
874
     * @example
875
     * ```typescript
876
     * this.grid.toggleAllGroupRows;
877
     * ```
878
     */
879
    public toggleAllGroupRows() {
880
        this.groupingExpansionState = [];
17✔
881
        this.groupsExpanded = !this.groupsExpanded;
17✔
882
        this.notifyChanges();
17✔
883
    }
884

885
    /** @hidden @internal */
886
    public get hasGroupableColumns(): boolean {
887
        return this._columns.some((col) => col.groupable && !col.columnGroup);
167,639✔
888
    }
889

890
    /**
891
     * Returns whether the grid has group area.
892
     *
893
     * @example
894
     * ```typescript
895
     * let isGroupAreaVisible = this.grid.showGroupArea;
896
     * ```
897
     *
898
     * @example
899
     * ```html
900
     * <igx-grid #grid [data]="Data" [showGroupArea]="false"></igx-grid>
901
     * ```
902
     */
903
    @Input({ transform: booleanAttribute })
904
    public get showGroupArea(): boolean {
905
        return this._showGroupArea;
26,269✔
906
    }
907
    public set showGroupArea(value: boolean) {
908
        this._showGroupArea = value;
1✔
909
        this.notifyChanges(true);
1✔
910
    }
911

912
    /**
913
     * @hidden @internal
914
     */
915
    public override isColumnGrouped(fieldName: string): boolean {
916
        return this.groupingExpressions.find(exp => exp.fieldName === fieldName) ? true : false;
2!
917
    }
918

919
    /**
920
     * @hidden @internal
921
     */
922
    public getContext(rowData: any, rowIndex: number, pinned?: boolean): any {
923
        if (this.isDetailRecord(rowData)) {
152,786✔
924
            const cachedData = this.childDetailTemplates.get(rowData.detailsData);
2,366✔
925
            const rowID = this.primaryKey ? rowData.detailsData[this.primaryKey] : rowData.detailsData;
2,366✔
926
            if (cachedData) {
2,366✔
927
                const view = cachedData.view;
2,163✔
928
                const tmlpOutlet = cachedData.owner;
2,163✔
929
                return {
2,163✔
930
                    $implicit: rowData.detailsData,
931
                    moveView: view,
932
                    owner: tmlpOutlet,
933
                    index: this.dataView.indexOf(rowData),
934
                    templateID: {
935
                        type: 'detailRow',
936
                        id: rowID
937
                    }
938
                };
939
            } else {
940
                // child rows contain unique grids, hence should have unique templates
941
                return {
203✔
942
                    $implicit: rowData.detailsData,
943
                    templateID: {
944
                        type: 'detailRow',
945
                        id: rowID
946
                    },
947
                    index: this.dataView.indexOf(rowData)
948
                };
949
            }
950
        }
951
        return {
150,420✔
952
            $implicit: this.isGhostRecord(rowData) || this.isRecordMerged(rowData) ? rowData.recordRef : rowData,
450,723✔
953
            index: this.getDataViewIndex(rowIndex, pinned),
954
            templateID: {
955
                type: this.isGroupByRecord(rowData) ? 'groupRow' : this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow',
292,147✔
956
                id: null
957
            },
958
            disabled: this.isGhostRecord(rowData),
959
            metaData: this.isRecordMerged(rowData) ? rowData : null
150,420✔
960
        };
961
    }
962

963
    /**
964
     * @hidden @internal
965
     */
966
    public viewCreatedHandler(args) {
967
        if (args.context.templateID.type === 'detailRow') {
20,407✔
968
            this.childDetailTemplates.set(args.context.$implicit, args);
203✔
969
        }
970
    }
971

972
    /**
973
     * @hidden @internal
974
     */
975
    public viewMovedHandler(args) {
976
        if (args.context.templateID.type === 'detailRow') {
64✔
977
            // view was moved, update owner in cache
978
            const key = args.context.$implicit;
64✔
979
            const cachedData = this.childDetailTemplates.get(key);
64✔
980
            cachedData.owner = args.owner;
64✔
981
        }
982
    }
983

984
    /**
985
     * @hidden @internal
986
     */
987
    public get iconTemplate() {
988
        if (this.groupsExpanded) {
2,109✔
989
            return this.headerExpandedIndicatorTemplate || this.defaultExpandedTemplate;
2,050✔
990
        } else {
991
            return this.headerCollapsedIndicatorTemplate || this.defaultCollapsedTemplate;
59✔
992
        }
993
    }
994

995
    /**
996
     * @hidden @internal
997
     */
998
    public override ngAfterContentInit() {
999
        super.ngAfterContentInit();
2,130✔
1000
        if (this.allowFiltering && this.hasColumnLayouts) {
2,130✔
1001
            this.filterMode = FilterMode.excelStyleFilter;
2✔
1002
        }
1003
        if (this.groupTemplate) {
2,130✔
1004
            this._groupRowTemplate = this.groupTemplate.template;
2✔
1005
        }
1006

1007
        if (this.detailTemplateDirective) {
2,130✔
1008
            this._detailTemplate = this.detailTemplateDirective;
61✔
1009
        }
1010

1011

1012
        if (this.hideGroupedColumns && this._columns && this.groupingExpressions) {
2,130✔
1013
            this._setGroupColsVisibility(this.hideGroupedColumns);
11✔
1014
        }
1015
        this._setupNavigationService();
2,130✔
1016
    }
1017

1018
    /**
1019
     * @hidden @internal
1020
     */
1021
    public override ngAfterViewInit() {
1022
        super.ngAfterViewInit();
2,144✔
1023
        this.verticalScrollContainer.beforeViewDestroyed.pipe(takeUntil(this.destroy$)).subscribe((view) => {
2,144✔
1024
            const rowData = view.context.$implicit;
2,861✔
1025
            if (this.isDetailRecord(rowData)) {
2,861✔
1026
                const cachedData = this.childDetailTemplates.get(rowData.detailsData);
6✔
1027
                if (cachedData) {
6✔
1028
                    const tmlpOutlet = cachedData.owner;
6✔
1029
                    tmlpOutlet._viewContainerRef.detach(0);
6✔
1030
                }
1031
            }
1032
        });
1033

1034
        this.sortingExpressionsChange.pipe(takeUntil(this.destroy$)).subscribe((sortingExpressions: ISortingExpression[]) => {
2,144✔
1035
            if (!this.groupingExpressions || !this.groupingExpressions.length) {
386✔
1036
                return;
143✔
1037
            }
1038

1039
            sortingExpressions.forEach((sortExpr: ISortingExpression) => {
243✔
1040
                const fieldName = sortExpr.fieldName;
13✔
1041
                const groupingExpr = this.groupingExpressions.find(ex => ex.fieldName === fieldName);
15✔
1042
                if (groupingExpr) {
13✔
1043
                    groupingExpr.dir = sortExpr.dir;
7✔
1044
                }
1045
            });
1046
        });
1047
    }
1048

1049
    /**
1050
     * @hidden @internal
1051
     */
1052
    public override ngOnInit() {
1053
        super.ngOnInit();
2,130✔
1054
        this.trackChanges = (_, rec) => (rec?.detailsData !== undefined ? rec.detailsData : rec);
341,107✔
1055
        this.groupingDone.pipe(takeUntil(this.destroy$)).subscribe((args) => {
2,130✔
1056
            this.crudService.endEdit(false);
274✔
1057
            this.summaryService.updateSummaryCache(args);
274✔
1058
            this._headerFeaturesWidth = NaN;
274✔
1059
        });
1060
    }
1061

1062
    /**
1063
     * @hidden @internal
1064
     */
1065
    public override ngDoCheck(): void {
1066
        if (this.groupingDiffer && this._columns && !this.hasColumnLayouts) {
9,089✔
1067
            const changes = this.groupingDiffer.diff(this.groupingExpressions);
37✔
1068
            if (changes && this._columns.length > 0) {
37✔
1069
                changes.forEachAddedItem((rec) => {
14✔
1070
                    const col = this.getColumnByName(rec.item.fieldName);
21✔
1071
                    if (col) {
21✔
1072
                        col.hidden = true;
20✔
1073
                    }
1074
                });
1075
                changes.forEachRemovedItem((rec) => {
14✔
1076
                    const col = this.getColumnByName(rec.item.fieldName);
×
1077
                    col.hidden = false;
×
1078
                });
1079
            }
1080
        }
1081
        super.ngDoCheck();
9,089✔
1082
    }
1083

1084
    /**
1085
     * @hidden @internal
1086
     */
1087
    public dataLoading(event) {
1088
        this.dataPreLoad.emit(event);
136✔
1089
    }
1090

1091
    /**
1092
     *
1093
     * Returns an array of the current cell selection in the form of `[{ column.field: cell.value }, ...]`.
1094
     *
1095
     * @remarks
1096
     * If `formatters` is enabled, the cell value will be formatted by its respective column formatter (if any).
1097
     * If `headers` is enabled, it will use the column header (if any) instead of the column field.
1098
     */
1099
    public override getSelectedData(formatters = false, headers = false): any[] {
338✔
1100
        if (this.groupingExpressions.length || this.hasDetails) {
182✔
1101
            const source = [];
15✔
1102

1103
            const process = (record) => {
15✔
1104
                if (record.expression || record.summaries || this.isDetailRecord(record)) {
220✔
1105
                    source.push(null);
97✔
1106
                    return;
97✔
1107
                }
1108
                source.push(record);
123✔
1109

1110
            };
1111

1112
            this.dataView.forEach(process);
15✔
1113
            return this.extractDataFromSelection(source, formatters, headers);
15✔
1114
        } else {
1115
            return super.getSelectedData(formatters, headers);
167✔
1116
        }
1117
    }
1118

1119
    /**
1120
     * Returns the grid row by index.
1121
     *
1122
     * @example
1123
     * ```typescript
1124
     * const myRow = grid.getRowByIndex(1);
1125
     * ```
1126
     * @param index
1127
     */
1128
    public getRowByIndex(index: number): RowType {
1129
        let row: RowType;
1130
        if (index < 0) {
604!
1131
            return undefined;
×
1132
        }
1133
        if (this.dataView.length >= this.virtualizationState.startIndex + this.virtualizationState.chunkSize) {
604!
1134
            row = this.createRow(index);
604✔
1135
        } else {
1136
            if (!(index < this.virtualizationState.startIndex) && !(index > this.virtualizationState.startIndex + this.virtualizationState.chunkSize)) {
×
1137
                row = this.createRow(index);
×
1138
            }
1139
        }
1140

1141
        if (this.pagingMode === 'remote' && this.page !== 0) {
604✔
1142
            row.index = index + this.perPage * this.page;
2✔
1143
        }
1144
        return row;
604✔
1145
    }
1146

1147
    /**
1148
     * Returns grid row object by the specified primary key.
1149
     *
1150
     * @remarks
1151
     * Requires that the `primaryKey` property is set.
1152
     * @example
1153
     * ```typescript
1154
     * const myRow = this.grid1.getRowByKey("cell5");
1155
     * ```
1156
     * @param keyValue
1157
     */
1158
    public getRowByKey(key: any): RowType {
1159
        const rec = this.filteredSortedData ? this.primaryKey ?
171✔
1160
            this.filteredSortedData.find(record => record[this.primaryKey] === key) :
311✔
1161
            this.filteredSortedData.find(record => record === key) : undefined;
232✔
1162
        const index = this.dataView.indexOf(rec);
171✔
1163
        if (index < 0 || index > this.dataView.length) {
171✔
1164
            return undefined;
10✔
1165
        }
1166

1167
        return new IgxGridRow(this, index, rec);
161✔
1168
    }
1169

1170
    /**
1171
     * @hidden @internal
1172
     */
1173
    public allRows(): RowType[] {
1174
        return this.dataView.map((_rec, index) => {
78✔
1175
            this.pagingMode === 'remote' && this.page !== 0 ?
3,909!
1176
                index = index + this.perPage * this.page : index = this.dataRowList.first.index + index;
1177
            return this.createRow(index);
3,909✔
1178
        });
1179
    }
1180

1181
    /**
1182
     * Returns the collection of grid rows for current page.
1183
     *
1184
     * @hidden @internal
1185
     */
1186
    public dataRows(): RowType[] {
1187
        return this.allRows().filter(row => row instanceof IgxGridRow);
3,909✔
1188
    }
1189

1190
    /**
1191
     * Returns an array of the selected grid cells.
1192
     *
1193
     * @example
1194
     * ```typescript
1195
     * const selectedCells = this.grid.selectedCells;
1196
     * ```
1197
     */
1198
    public get selectedCells(): CellType[] {
1199
        return this.dataRows().map((row) => row.cells.filter((cell) => cell.selected))
34,455✔
1200
            .reduce((a, b) => a.concat(b), []);
2,213✔
1201
    }
1202

1203
    /**
1204
     * Returns a `CellType` object that matches the conditions.
1205
     *
1206
     * @example
1207
     * ```typescript
1208
     * const myCell = this.grid1.getCellByColumn(2, "UnitPrice");
1209
     * ```
1210
     * @param rowIndex
1211
     * @param columnField
1212
     */
1213
    public getCellByColumn(rowIndex: number, columnField: string): CellType {
1214
        const row = this.getRowByIndex(rowIndex);
464✔
1215
        const column = this._columns.find((col) => col.field === columnField);
1,418✔
1216
        if (row && row instanceof IgxGridRow && !row.data?.detailsData && column) {
464✔
1217
            if (this.pagingMode === 'remote' && this.page !== 0) {
464!
1218
                row.index = rowIndex + this.perPage * this.page;
×
1219
            }
1220
            return new IgxGridCell(this, row.index, column);
464✔
1221
        }
1222
    }
1223

1224
    /**
1225
     * Returns a `CellType` object that matches the conditions.
1226
     *
1227
     * @remarks
1228
     * Requires that the primaryKey property is set.
1229
     * @example
1230
     * ```typescript
1231
     * grid.getCellByKey(1, 'index');
1232
     * ```
1233
     * @param rowSelector match any rowID
1234
     * @param columnField
1235
     */
1236
    public getCellByKey(rowSelector: any, columnField: string): CellType {
1237
        const row = this.getRowByKey(rowSelector);
15✔
1238
        const column = this._columns.find((col) => col.field === columnField);
39✔
1239
        if (row && column) {
15✔
1240
            return new IgxGridCell(this, row.index, column);
15✔
1241
        }
1242
    }
1243

1244
    public override pinRow(rowID: any, index?: number): boolean {
1245
        const row = this.getRowByKey(rowID);
99✔
1246
        return super.pinRow(rowID, index, row);
99✔
1247
    }
1248

1249
    public override unpinRow(rowID: any): boolean {
1250
        const row = this.getRowByKey(rowID);
18✔
1251
        return super.unpinRow(rowID, row);
18✔
1252
    }
1253

1254
    /**
1255
     * @hidden @internal
1256
     */
1257
    public createRow(index: number, data?: any): RowType {
1258
        let row: RowType;
1259

1260
        const dataIndex = this._getDataViewIndex(index);
46,831✔
1261
        const rec = data ?? this.dataView[dataIndex];
46,831✔
1262

1263
        if (rec && this.isGroupByRecord(rec)) {
46,831✔
1264
            row = new IgxGroupByRow(this, index, rec);
127✔
1265
        }
1266
        if (rec && this.isSummaryRow(rec)) {
46,831✔
1267
            row = new IgxSummaryRow(this, index, rec.summaries);
20✔
1268
        }
1269
        // if found record is a no a groupby or summary row, return IgxGridRow instance
1270
        if (!row && rec) {
46,831✔
1271
            row = new IgxGridRow(this, index, rec);
45,111✔
1272
        }
1273

1274
        return row;
46,831✔
1275
    }
1276

1277
    /**
1278
     * @hidden @internal
1279
     */
1280
    protected override get defaultTargetBodyHeight(): number {
1281
        const allItems = this.totalItemCount || this.dataLength;
184✔
1282
        return this.renderedActualRowHeight * Math.min(this._defaultTargetRecordNumber,
184✔
1283
            this.paginator ? Math.min(allItems, this.perPage) : allItems);
184✔
1284
    }
1285

1286
    /**
1287
     * @hidden @internal
1288
     */
1289
    protected override getGroupAreaHeight(): number {
1290
        return this.groupArea ? this.getComputedHeight(this.groupArea.nativeElement) : 0;
5,621✔
1291
    }
1292

1293
    /**
1294
     * @hidden @internal
1295
     */
1296
    protected override onColumnsAddedOrRemoved() {
1297
        // update grouping states
1298
        this.groupablePipeTrigger++;
64✔
1299
        if (this.groupingExpressions && this.hideGroupedColumns) {
64✔
1300
            this._setGroupColsVisibility(this.hideGroupedColumns);
2✔
1301
        }
1302
        super.onColumnsAddedOrRemoved();
64✔
1303
    }
1304

1305
    /**
1306
     * @hidden
1307
     */
1308
    protected override onColumnsChanged(change: QueryList<IgxColumnComponent>) {
1309
        super.onColumnsChanged(change);
65✔
1310

1311
        if (this.hasColumnLayouts && !(this.navigation instanceof IgxGridMRLNavigationService)) {
65!
1312
            this._setupNavigationService();
×
1313
        }
1314
    }
1315

1316
    /**
1317
     * @hidden @internal
1318
     */
1319
    protected override scrollTo(row: any | number, column: any | number): void {
1320
        if (this.groupingExpressions && this.groupingExpressions.length
108✔
1321
            && typeof (row) !== 'number') {
1322
            const rowIndex = this.groupingResult.indexOf(row);
31✔
1323
            const groupByRecord = this.groupingMetadata[rowIndex];
31✔
1324
            if (groupByRecord) {
31✔
1325
                this._fullyExpandGroup(groupByRecord);
31✔
1326
            }
1327
        }
1328

1329
        super.scrollTo(row, column, this.groupingFlatResult);
108✔
1330
    }
1331

1332
    /**
1333
     * @hidden @internal
1334
     */
1335
    protected _getStateForGroupRow(groupRow: IGroupByRecord): IGroupByExpandState {
1336
        return this._gridAPI.groupBy_get_expanded_for_group(groupRow);
6,392✔
1337
    }
1338

1339
    /**
1340
     * @hidden
1341
     */
1342
    protected _toggleGroup(groupRow: IGroupByRecord) {
1343
        this._gridAPI.groupBy_toggle_group(groupRow);
60✔
1344
    }
1345

1346
    /**
1347
     * @hidden @internal
1348
     */
1349
    protected _fullyExpandGroup(groupRow: IGroupByRecord) {
1350
        this._gridAPI.groupBy_fully_expand_group(groupRow);
31✔
1351
    }
1352

1353
    /**
1354
     * @hidden @internal
1355
     */
1356
    protected _applyGrouping() {
1357
        this._gridAPI.sort_groupBy_multiple(this._groupingExpressions);
412✔
1358
    }
1359

1360
    protected _setupNavigationService() {
1361
        if (this.hasColumnLayouts) {
2,130✔
1362
            this.navigation = this.injector.get(IgxGridMRLNavigationService);
122✔
1363
            this.navigation.grid = this;
122✔
1364
        }
1365
    }
1366

1367
    private checkIfNoColumnField(expression: IGroupingExpression | Array<IGroupingExpression> | any): boolean {
1368
        if (expression instanceof Array) {
238✔
1369
            for (const singleExpression of expression) {
21✔
1370
                if (!singleExpression.fieldName) {
54✔
1371
                    return true;
1✔
1372
                }
1373
            }
1374
            return false;
20✔
1375
        }
1376
        return !expression.fieldName;
217✔
1377
    }
1378

1379
    private _setGroupColsVisibility(value) {
1380
        if (this._columns.length > 0 && !this.hasColumnLayouts) {
50✔
1381
            this.groupingExpressions.forEach((expr) => {
17✔
1382
                const col = this.getColumnByName(expr.fieldName);
6✔
1383
                col.hidden = value;
6✔
1384
            });
1385
        }
1386
    }
1387

1388
    private stringifyCallback(key: string, val: any) {
1389
        // Workaround for Blazor, since its wrappers inject this externalObject that cannot serialize.
1390
        if (key === 'externalObject') {
2,664!
1391
            return undefined;
×
1392
        }
1393
        return val;
2,664✔
1394
    }
1395
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc