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

IgniteUI / igniteui-angular / 28358286454

29 Jun 2026 08:17AM UTC coverage: 90.127% (-0.05%) from 90.174%
28358286454

Pull #17246

github

web-flow
Merge 21687d939 into 07bdcd752
Pull Request #17246: fix(pivot-grid): fix date format based on the localization

14921 of 17392 branches covered (85.79%)

Branch coverage included in aggregate %.

29 of 32 new or added lines in 4 files covered. (90.63%)

735 existing lines in 76 files now uncovered.

29992 of 32441 relevant lines covered (92.45%)

34632.46 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
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
    imports: [
117
        NgClass,
118
        NgStyle,
119
        NgTemplateOutlet,
120
        IgxGridGroupByAreaComponent,
121
        IgxGridHeaderRowComponent,
122
        IgxGridBodyDirective,
123
        IgxGridDragSelectDirective,
124
        IgxColumnMovingDropDirective,
125
        IgxGridForOfDirective,
126
        IgxTemplateOutletDirective,
127
        IgxGridRowComponent,
128
        IgxGridGroupByRowComponent,
129
        IgxSummaryRowComponent,
130
        IgxOverlayOutletDirective,
131
        IgxToggleDirective,
132
        IgxCircularProgressBarComponent,
133
        IgxSnackbarComponent,
134
        IgxButtonDirective,
135
        IgxRippleDirective,
136
        IgxIconComponent,
137
        IgxRowEditTabStopDirective,
138
        IgxGridColumnResizerComponent,
139
        IgxGridTransactionPipe,
140
        IgxHasVisibleColumnsPipe,
141
        IgxGridRowPinningPipe,
142
        IgxGridAddRowPipe,
143
        IgxGridRowClassesPipe,
144
        IgxGridRowStylesPipe,
145
        IgxSummaryDataPipe,
146
        IgxGridGroupingPipe,
147
        IgxGridPagingPipe,
148
        IgxGridSortingPipe,
149
        IgxGridFilteringPipe,
150
        IgxGridSummaryPipe,
151
        IgxGridDetailsPipe,
152
        IgxStringReplacePipe,
153
        IgxGridCellMergePipe,
154
        IgxGridUnmergeActivePipe,
155
        IgxScrollInertiaDirective
156
    ],
157
    schemas: [CUSTOM_ELEMENTS_SCHEMA]
158
})
159
export class IgxGridComponent extends IgxGridBaseDirective implements GridType, OnInit, DoCheck, AfterContentInit, AfterViewInit {
3✔
160
    /**
161
     * Emitted when a new chunk of data is loaded from virtualization.
162
     *
163
     * @example
164
     * ```typescript
165
     *  <igx-grid #grid [data]="localData" [autoGenerate]="true" (dataPreLoad)='handleDataPreloadEvent()'></igx-grid>
166
     * ```
167
     */
168
    @Output()
169
    public dataPreLoad = new EventEmitter<IForOfState>();
2,130✔
170

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

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

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

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

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

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

248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

685
    /**
686
     * @hidden @internal
687
     */
688
    public getRowTemplate(rowData) {
689
        if (this.isGroupByRecord(rowData)) {
151,949✔
690
            return this.defaultGroupTemplate;
8,682✔
691
        } else if (this.isSummaryRow(rowData)) {
143,267✔
692
            return this.summaryTemplate;
1,536✔
693
        } else if (this.hasDetails && this.isDetailRecord(rowData)) {
141,731✔
694
            return this.detailTemplateContainer;
2,310✔
695
        } else {
696
            return this.recordTemplate;
139,421✔
697
        }
698
    }
699

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1010

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

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

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

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

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

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

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

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

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

1109
            };
1110

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1273
        return row;
46,832✔
1274
    }
1275

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1387
    private stringifyCallback(key: string, val: any) {
1388
        // Workaround for Blazor, since its wrappers inject this externalObject that cannot serialize.
1389
        if (key === 'externalObject') {
2,664!
UNCOV
1390
            return undefined;
×
1391
        }
1392
        return val;
2,664✔
1393
    }
1394
}
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