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

IgniteUI / igniteui-angular / 18843356031

27 Oct 2025 01:52PM UTC coverage: 91.757% (+0.008%) from 91.749%
18843356031

push

github

web-flow
fix(grid): re-init nav service after columns change in WC with layouts (#16253)

13562 of 15845 branches covered (85.59%)

2 of 3 new or added lines in 1 file covered. (66.67%)

83 existing lines in 3 files now uncovered.

27207 of 29651 relevant lines covered (91.76%)

34906.64 hits per line

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

96.19
/projects/igniteui-angular/src/lib/grids/grid/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

8
import { IgxGridBaseDirective } from '../grid-base.directive';
9
import { IgxGridNavigationService } from '../grid-navigation.service';
10
import { IgxGridAPIService } from './grid-api.service';
11
import { cloneArray, IBaseEventArgs } from '../../core/utils';
12
import { IGroupByRecord } from '../../data-operations/groupby-record.interface';
13
import { IgxGroupByRowTemplateDirective, IgxGridDetailTemplateDirective } from '../grid.directives';
14
import { IgxGridGroupByRowComponent } from './groupby-row.component';
15
import { IGroupByExpandState } from '../../data-operations/groupby-expand-state.interface';
16
import { IForOfState, IgxGridForOfDirective } from '../../directives/for-of/for_of.directive';
17
import { IgxColumnComponent } from '../columns/column.component';
18
import { take, takeUntil } from 'rxjs/operators';
19
import { IgxFilteringService } from '../filtering/grid-filtering.service';
20
import { IGroupingExpression } from '../../data-operations/grouping-expression.interface';
21
import { IgxColumnResizingService } from '../resizing/resizing.service';
22
import { IgxGridSummaryService } from '../summaries/grid-summary.service';
23
import { IgxGridSelectionService } from '../selection/selection.service';
24
import { IgxForOfSyncService, IgxForOfScrollSyncService } from '../../directives/for-of/for_of.sync.service';
25
import { IgxGridMRLNavigationService } from '../grid-mrl-navigation.service';
26
import { FilterMode } from '../common/enums';
27
import { CellType, GridType, IgxGridMasterDetailContext, IgxGroupByRowSelectorTemplateContext, IgxGroupByRowTemplateContext, IGX_GRID_BASE, IGX_GRID_SERVICE_BASE, RowType } from '../common/grid.interface';
28
import { IgxGroupByRowSelectorDirective } from '../selection/row-selectors';
29
import { IgxGridCRUDService } from '../common/crud.service';
30
import { IgxGridRow, IgxGroupByRow, IgxSummaryRow } from '../grid-public-row';
31
import { IgxGridCell } from '../grid-public-cell';
32
import { ISortingExpression } from '../../data-operations/sorting-strategy';
33
import { IGridGroupingStrategy } from '../common/strategy';
34
import { IgxGridValidationService } from './grid-validation.service';
35
import { IgxGridDetailsPipe } from './grid.details.pipe';
36
import { IgxGridSummaryPipe } from './grid.summary.pipe';
37
import { IgxGridGroupingPipe, IgxGridPagingPipe, IgxGridSortingPipe, IgxGridFilteringPipe } from './grid.pipes';
38
import { IgxSummaryDataPipe } from '../summaries/grid-root-summary.pipe';
39
import { IgxGridTransactionPipe, IgxHasVisibleColumnsPipe, IgxGridRowPinningPipe, IgxGridAddRowPipe, IgxGridRowClassesPipe, IgxGridRowStylesPipe, IgxStringReplacePipe } from '../common/pipes';
40
import { IgxGridColumnResizerComponent } from '../resizing/resizer.component';
41
import { IgxRowEditTabStopDirective } from '../grid.rowEdit.directive';
42
import { IgxIconComponent } from '../../icon/icon.component';
43
import { IgxRippleDirective } from '../../directives/ripple/ripple.directive';
44
import { IgxButtonDirective } from '../../directives/button/button.directive';
45
import { IgxSnackbarComponent } from '../../snackbar/snackbar.component';
46
import { IgxCircularProgressBarComponent } from '../../progressbar/progressbar.component';
47
import { IgxOverlayOutletDirective, IgxToggleDirective } from '../../directives/toggle/toggle.directive';
48
import { IgxSummaryRowComponent } from '../summaries/summary-row.component';
49
import { IgxGridRowComponent } from './grid-row.component';
50
import { IgxTemplateOutletDirective } from '../../directives/template-outlet/template_outlet.directive';
51
import { IgxColumnMovingDropDirective } from '../moving/moving.drop.directive';
52
import { IgxGridDragSelectDirective } from '../selection/drag-select.directive';
53
import { IgxGridBodyDirective } from '../grid.common';
54
import { IgxGridHeaderRowComponent } from '../headers/grid-header-row.component';
55
import { IgxGridGroupByAreaComponent } from '../grouping/grid-group-by-area.component';
56
import { Observable, Subject } from 'rxjs';
57

58
let NEXT_ID = 0;
3✔
59

60
export interface IGroupingDoneEventArgs extends IBaseEventArgs {
61
    expressions: Array<ISortingExpression> | ISortingExpression;
62
    groupedColumns: Array<IgxColumnComponent> | IgxColumnComponent;
63
    ungroupedColumns: Array<IgxColumnComponent> | IgxColumnComponent;
64
}
65

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

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

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

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

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

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

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

247

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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;
127,246✔
404
    }
405

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

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

419
        this.cdr.markForCheck();
2,112✔
420
        if (this.isPercentHeight) {
2,112✔
421
            this.notifyChanges(true);
938✔
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,112✔
425
            this.recalculateAutoSizes();
1✔
426
        }
427
        this.checkPrimaryKeyField();
2,112✔
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;
4✔
444
    }
445

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

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

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

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

461
    /**
462
     * @hidden @internal
463
     */
464
    public groupingPerformed$: Observable<void> = this.groupingPerformedSubject.asObservable();
2,024✔
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,855,616✔
485
    }
486

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

543
    public set groupingExpansionState(value) {
544
        if (value !== this._groupingExpandState) {
96✔
545
            this.groupingExpansionStateChange.emit(value);
96✔
546
        }
547
        this._groupingExpandState = value;
96✔
548
        if (this.gridAPI.grid) {
96✔
549
            this.cdr.detectChanges();
96✔
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,106✔
566
    }
567

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

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

581
    /**
582
     * Gets/Sets the grouping strategy of the grid.
583
     *
584
     * @remarks The default IgxGrouping extends from IgxSorting 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;
25,742✔
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,858✔
620
    }
621

622
    /**
623
     * @hidden @internal
624
     */
625
    public get groupsRowList() {
626
        const res = new QueryList<any>();
148✔
627
        if (!this._groupsRowList) {
148!
628
            return res;
×
629
        }
630
        const rList = this._groupsRowList.filter(item => item.element.nativeElement.parentElement !== null)
630✔
631
            .sort((item1, item2) => item1.index - item2.index);
607✔
632
        res.reset(rList);
148✔
633
        return res;
148✔
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;
389✔
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,379✔
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;
307,834✔
683
    }
684

685
    /**
686
     * @hidden @internal
687
     */
688
    public getRowTemplate(rowData) {
689
        if (this.isGroupByRecord(rowData)) {
151,128✔
690
            return this.defaultGroupTemplate;
9,139✔
691
        } else if (this.isSummaryRow(rowData)) {
141,989✔
692
            return this.summaryTemplate;
1,463✔
693
        } else if (this.hasDetails && this.isDetailRecord(rowData)) {
140,526✔
694
            return this.detailTemplateContainer;
2,379✔
695
        } else {
696
            return this.recordTemplate;
138,147✔
697
        }
698
    }
699

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

707
    /**
708
     * @hidden @internal
709
     */
710
    public isDetailActive(rowIndex) {
711
        return this.navigation.activeNode ? this.navigation.activeNode.row === rowIndex : false;
2,379!
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,240✔
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 `IgxColumnComponent` 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)) {
233✔
754
            return;
1✔
755
        }
756
        this.crudService.endEdit(false);
232✔
757
        if (expression instanceof Array) {
232✔
758
            this._gridAPI.groupBy_multiple(expression);
20✔
759
        } else {
760
            this._gridAPI.groupBy(expression);
212✔
761
        }
762
        this.notifyChanges(true);
231✔
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);
13,259✔
798
        return state ? state.expanded : this.groupsExpanded;
13,259✔
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);
58✔
813
        this.notifyChanges();
58✔
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) {
857
        this._fullyExpandGroup(groupRow);
×
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 &&
365,773✔
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);
159,605✔
887
    }
888

889
    /**
890
     * Returns whether the `IgxGridComponent` 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;
25,741✔
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)) {
151,721✔
923
            const cachedData = this.childDetailTemplates.get(rowData.detailsData);
2,379✔
924
            const rowID = this.primaryKey ? rowData.detailsData[this.primaryKey] : rowData.detailsData;
2,379✔
925
            if (cachedData) {
2,379✔
926
                const view = cachedData.view;
2,177✔
927
                const tmlpOutlet = cachedData.owner;
2,177✔
928
                return {
2,177✔
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 {
202✔
941
                    $implicit: rowData.detailsData,
942
                    templateID: {
943
                        type: 'detailRow',
944
                        id: rowID
945
                    },
946
                    index: this.dataView.indexOf(rowData)
947
                };
948
            }
949
        }
950
        return {
149,342✔
951
            $implicit: this.isGhostRecord(rowData) ? rowData.recordRef : rowData,
149,342✔
952
            index: this.getDataViewIndex(rowIndex, pinned),
953
            templateID: {
954
                type: this.isGroupByRecord(rowData) ? 'groupRow' : this.isSummaryRow(rowData) ? 'summaryRow' : 'dataRow',
289,545✔
955
                id: null
956
            },
957
            disabled: this.isGhostRecord(rowData)
958
        };
959
    }
960

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

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

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

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

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

1009

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

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

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

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

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

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

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

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

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

1108
            };
1109

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

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

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

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

1165
        return new IgxGridRow(this, index, rec);
151✔
1166
    }
1167

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

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

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

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

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

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

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

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

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

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

1272
        return row;
46,729✔
1273
    }
1274

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

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

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

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

1309
        if (this.hasColumnLayouts && !(this.navigation instanceof IgxGridMRLNavigationService)) {
64!
NEW
1310
            this._setupNavigationService();
×
1311
        }
1312
    }
1313

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

1327
        super.scrollTo(row, column, this.groupingFlatResult);
103✔
1328
    }
1329

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

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

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

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

1358
    private _setupNavigationService() {
1359
        if (this.hasColumnLayouts) {
2,024✔
1360
            this.navigation = new IgxGridMRLNavigationService(this.platform);
119✔
1361
            this.navigation.grid = this;
119✔
1362
        }
1363
    }
1364

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

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

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

© 2026 Coveralls, Inc