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

IgniteUI / igniteui-angular / 17322895485

29 Aug 2025 11:43AM UTC coverage: 91.568% (-0.05%) from 91.618%
17322895485

push

github

web-flow
Grid Cell merging  (#16024)

13819 of 16209 branches covered (85.26%)

234 of 254 new or added lines in 11 files covered. (92.13%)

16 existing lines in 2 files now uncovered.

27757 of 30313 relevant lines covered (91.57%)

34712.72 hits per line

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

90.82
/projects/igniteui-angular/src/lib/grids/cell.component.ts
1
import { useAnimation } from '@angular/animations';
2
import {
3
    ChangeDetectionStrategy,
4
    ChangeDetectorRef,
5
    Component,
6
    ElementRef,
7
    HostBinding,
8
    HostListener,
9
    Input,
10
    TemplateRef,
11
    ViewChild,
12
    NgZone,
13
    OnInit,
14
    OnDestroy,
15
    OnChanges,
16
    SimpleChanges,
17
    Inject,
18
    ViewChildren,
19
    QueryList,
20
    AfterViewInit,
21
    booleanAttribute
22
} from '@angular/core';
23
import { formatPercent, NgClass, NgTemplateOutlet, DecimalPipe, PercentPipe, CurrencyPipe, DatePipe, getLocaleCurrencyCode, getCurrencySymbol } from '@angular/common';
24
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
25

26
import { first, takeUntil, takeWhile } from 'rxjs/operators';
27
import { Subject } from 'rxjs';
28

29
import { IgxTextHighlightDirective } from '../directives/text-highlight/text-highlight.directive';
30
import { formatCurrency, formatDate, PlatformUtil } from '../core/utils';
31
import { IgxGridSelectionService } from './selection/selection.service';
32
import { HammerGesturesManager } from '../core/touch';
33
import { GridSelectionMode } from './common/enums';
34
import { CellType, ColumnType, GridType, IgxCellTemplateContext, IGX_GRID_BASE, RowType } from './common/grid.interface';
35
import { GridColumnDataType } from '../data-operations/data-util';
36
import { IgxRowDirective } from './row.directive';
37
import { ISearchInfo } from './common/events';
38
import { IgxGridCell } from './grid-public-cell';
39
import { ISelectionNode } from './common/types';
40
import { AutoPositionStrategy, HorizontalAlignment, IgxOverlayService } from '../services/public_api';
41
import { IgxIconComponent } from '../icon/icon.component';
42
import { IgxGridCellImageAltPipe, IgxStringReplacePipe, IgxColumnFormatterPipe } from './common/pipes';
43
import { IgxTooltipDirective } from '../directives/tooltip/tooltip.directive';
44
import { IgxTooltipTargetDirective } from '../directives/tooltip/tooltip-target.directive';
45
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
46
import { IgxPrefixDirective } from '../directives/prefix/prefix.directive';
47
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/date-time-editor.directive';
48
import { IgxTimePickerComponent } from '../time-picker/time-picker.component';
49
import { IgxDatePickerComponent } from '../date-picker/date-picker.component';
50
import { IgxCheckboxComponent } from '../checkbox/checkbox.component';
51
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
52
import { IgxFocusDirective } from '../directives/focus/focus.directive';
53
import { IgxInputDirective } from '../directives/input/input.directive';
54
import { IgxInputGroupComponent } from '../input-group/input-group.component';
55
import { IgxChipComponent } from '../chips/chip.component';
56
import { fadeOut, scaleInCenter } from 'igniteui-angular/animations';
57

58
/**
59
 * Providing reference to `IgxGridCellComponent`:
60
 * ```typescript
61
 * @ViewChild('grid', { read: IgxGridComponent })
62
 *  public grid: IgxGridComponent;
63
 * ```
64
 * ```typescript
65
 *  let column = this.grid.columnList.first;
66
 * ```
67
 * ```typescript
68
 *  let cell = column.cells[0];
69
 * ```
70
 */
71
@Component({
72
    changeDetection: ChangeDetectionStrategy.OnPush,
73
    selector: 'igx-grid-cell',
74
    templateUrl: './cell.component.html',
75
    providers: [HammerGesturesManager],
76
    imports: [
77
        NgClass,
78
        NgTemplateOutlet,
79
        DecimalPipe,
80
        PercentPipe,
81
        CurrencyPipe,
82
        DatePipe,
83
        ReactiveFormsModule,
84
        IgxChipComponent,
85
        IgxTextHighlightDirective,
86
        IgxIconComponent,
87
        IgxInputGroupComponent,
88
        IgxInputDirective,
89
        IgxFocusDirective,
90
        IgxTextSelectionDirective,
91
        IgxCheckboxComponent,
92
        IgxDatePickerComponent,
93
        IgxTimePickerComponent,
94
        IgxDateTimeEditorDirective,
95
        IgxPrefixDirective,
96
        IgxSuffixDirective,
97
        IgxTooltipTargetDirective,
98
        IgxTooltipDirective,
99
        IgxGridCellImageAltPipe,
100
        IgxStringReplacePipe,
101
        IgxColumnFormatterPipe
102
    ]
103
})
104
export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy, CellType, AfterViewInit {
3✔
105
    private _destroy$ = new Subject<void>();
155,521✔
106
    /**
107
     * @hidden
108
     * @internal
109
     */
110
    @HostBinding('class.igx-grid__td--new')
111
    public get isEmptyAddRowCell() {
112
        return this.intRow.addRowUI && (this.value === undefined || this.value === null);
1,226,424✔
113
    }
114

115
    /**
116
     * @hidden
117
     * @internal
118
     */
119
    @ViewChildren('error', { read: IgxTooltipDirective })
120
    public errorTooltip: QueryList<IgxTooltipDirective>;
121

122
    /**
123
     * @hidden
124
     * @internal
125
     */
126
    @ViewChild('errorIcon', { read: IgxIconComponent, static: false })
127
    public errorIcon: IgxIconComponent;
128

129
    /**
130
     * Gets the default error template.
131
     * @hidden @internal
132
     */
133
    @ViewChild('defaultError', { read: TemplateRef, static: true })
134
    public defaultErrorTemplate: TemplateRef<any>;
135

136
    /**
137
     * Gets the column of the cell.
138
     * ```typescript
139
     *  let cellColumn = this.cell.column;
140
     * ```
141
     *
142
     * @memberof IgxGridCellComponent
143
     */
144
    @Input()
145
    public column: ColumnType;
146

147
    /**
148
     * @hidden
149
     * @internal
150
     */
151
    @Input()
152
    public isPlaceholder: boolean;
153

154
    /**
155
        Gets whether this cell is a merged cell.
156
     */
157
    @Input()
158
    public isMerged: boolean;
159

160
    /**
161
     * @hidden
162
     * @internal
163
     */
164
    protected get formGroup(): FormGroup {
165
        return this.grid.validation.getFormGroup(this.intRow.key);
5,963,441✔
166
    }
167

168
    /**
169
     * @hidden
170
     * @internal
171
     */
172
    @Input()
173
    public intRow: IgxRowDirective;
174

175
    /**
176
     * Gets the row of the cell.
177
     * ```typescript
178
     * let cellRow = this.cell.row;
179
     * ```
180
     *
181
     * @memberof IgxGridCellComponent
182
     */
183
    @Input()
184
    public get row(): RowType {
185
        return this.grid.createRow(this.intRow.index);
8,319✔
186
    }
187

188
    /**
189
     * Gets the data of the row of the cell.
190
     * ```typescript
191
     * let rowData = this.cell.rowData;
192
     * ```
193
     *
194
     * @memberof IgxGridCellComponent
195
     */
196
    @Input()
197
    public rowData: any;
198

199
    /**
200
     * @hidden
201
     * @internal
202
     */
203
    @Input()
204
    public columnData: any;
205

206
    /**
207
     * Sets/gets the template of the cell.
208
     * ```html
209
     * <ng-template #cellTemplate igxCell let-value>
210
     *   <div style="font-style: oblique; color:blueviolet; background:red">
211
     *       <span>{{value}}</span>
212
     *   </div>
213
     * </ng-template>
214
     * ```
215
     * ```typescript
216
     * @ViewChild('cellTemplate',{read: TemplateRef})
217
     * cellTemplate: TemplateRef<any>;
218
     * ```
219
     * ```typescript
220
     * this.cell.cellTemplate = this.cellTemplate;
221
     * ```
222
     * ```typescript
223
     * let template =  this.cell.cellTemplate;
224
     * ```
225
     *
226
     * @memberof IgxGridCellComponent
227
     */
228
    @Input()
229
    public cellTemplate: TemplateRef<any>;
230

231
    @Input()
232
    public cellValidationErrorTemplate: TemplateRef<any>;
233

234
    @Input()
235
    public pinnedIndicator: TemplateRef<any>;
236

237
    /**
238
     * Sets/gets the cell value.
239
     * ```typescript
240
     * this.cell.value = "Cell Value";
241
     * ```
242
     * ```typescript
243
     * let cellValue = this.cell.value;
244
     * ```
245
     *
246
     * @memberof IgxGridCellComponent
247
     */
248
    @Input()
249
    public value: any;
250

251
    /**
252
     * Gets the cell formatter.
253
     * ```typescript
254
     * let cellForamatter = this.cell.formatter;
255
     * ```
256
     *
257
     * @memberof IgxGridCellComponent
258
     */
259
    @Input()
260
    public formatter: (value: any, rowData?: any, columnData?: any) => any;
261

262
    /**
263
     * Gets the cell template context object.
264
     * ```typescript
265
     *  let context = this.cell.context();
266
     * ```
267
     *
268
     * @memberof IgxGridCellComponent
269
     */
270
    public get context(): IgxCellTemplateContext {
271
        const getCellType = () => this.getCellType(true);
658,119✔
272
        const ctx: IgxCellTemplateContext = {
658,119✔
273
            $implicit: this.value,
274
            additionalTemplateContext: this.column.additionalTemplateContext,
275
            get cell() {
276
                /* Turns the `cell` property from the template context object into lazy-evaluated one.
277
                 * Otherwise on each detection cycle the cell template is recreating N cell instances where
278
                 * N = number of visible cells in the grid, leading to massive performance degradation in large grids.
279
                 */
280
                return getCellType();
2,025✔
281
            }
282
        };
283
        if (this.editMode) {
658,119✔
284
            ctx.formControl = this.formControl;
2,485✔
285
        }
286
        if (this.isInvalid) {
658,119✔
287
            ctx.defaultErrorTemplate = this.defaultErrorTemplate;
429✔
288
        }
289
        return ctx;
658,119✔
290
    }
291

292
    /**
293
     * Gets the cell template.
294
     * ```typescript
295
     * let template = this.cell.template;
296
     * ```
297
     *
298
     * @memberof IgxGridCellComponent
299
     */
300
    public get template(): TemplateRef<any> {
301
        if (this.isPlaceholder) {
329,012✔
302
            return this.emptyCellTemplate;
204✔
303
        }
304
        if (this.editMode && this.formGroup) {
328,808✔
305
            const inlineEditorTemplate = this.column.inlineEditorTemplate;
1,241✔
306
            return inlineEditorTemplate ? inlineEditorTemplate : this.inlineEditorTemplate;
1,241✔
307
        }
308
        if (this.cellTemplate) {
327,567✔
309
            return this.cellTemplate;
802✔
310
        }
311
        if (this.grid.rowEditable && this.intRow.addRowUI) {
326,765✔
312
            return this.addRowCellTemplate;
1,918✔
313
        }
314
        return this.defaultCellTemplate;
324,847✔
315
    }
316

317
    /**
318
     * Gets the pinned indicator template.
319
     * ```typescript
320
     * let template = this.cell.pinnedIndicatorTemplate;
321
     * ```
322
     *
323
     * @memberof IgxGridCellComponent
324
     */
325
    public get pinnedIndicatorTemplate() {
326
        if (this.pinnedIndicator) {
328,964!
327
            return this.pinnedIndicator;
×
328
        }
329
        return this.defaultPinnedIndicator;
328,964✔
330
    }
331

332
    /**
333
     * Gets the `id` of the grid in which the cell is stored.
334
     * ```typescript
335
     * let gridId = this.cell.gridID;
336
     * ```
337
     *
338
     * @memberof IgxGridCellComponent
339
     */
340
    public get gridID(): any {
341
        return this.intRow.gridID;
307,116✔
342
    }
343

344

345
    /**
346
     * Gets the `index` of the row where the cell is stored.
347
     * ```typescript
348
     * let rowIndex = this.cell.rowIndex;
349
     * ```
350
     *
351
     * @memberof IgxGridCellComponent
352
     */
353
    @HostBinding('attr.data-rowIndex')
354
    public get rowIndex(): number {
355
        return this.intRow.index;
6,128,220✔
356
    }
357

358
    /**
359
     * Gets the `index` of the cell column.
360
     * ```typescript
361
     * let columnIndex = this.cell.columnIndex;
362
     * ```
363
     *
364
     * @memberof IgxGridCellComponent
365
     */
366
    public get columnIndex(): number {
367
        return this.column.index;
17,843✔
368
    }
369

370
    /**
371
     * Returns the column visible index.
372
     * ```typescript
373
     * let visibleColumnIndex = this.cell.visibleColumnIndex;
374
     * ```
375
     *
376
     * @memberof IgxGridCellComponent
377
     */
378
    @HostBinding('attr.data-visibleIndex')
379
    @Input()
380
    public get visibleColumnIndex() {
381
        return this.column.columnLayoutChild ? this.column.visibleIndex : this._vIndex;
4,906,371✔
382
    }
383

384
    public set visibleColumnIndex(val) {
385
        this._vIndex = val;
162,717✔
386
    }
387

388
    /**
389
     * Gets the ID of the cell.
390
     * ```typescript
391
     * let cellID = this.cell.cellID;
392
     * ```
393
     *
394
     * @memberof IgxGridCellComponent
395
     */
396
    public get cellID() {
397
        const primaryKey = this.grid.primaryKey;
462✔
398
        const rowID = primaryKey ? this.rowData[primaryKey] : this.rowData;
462✔
399
        return { rowID, columnID: this.columnIndex, rowIndex: this.rowIndex };
462✔
400
    }
401

402
    @HostBinding('attr.id')
403
    public get attrCellID() {
404
        return `${this.intRow.gridID}_${this.rowIndex}_${this.visibleColumnIndex}`;
1,224,553✔
405
    }
406

407
    @HostBinding('attr.title')
408
    public get title() {
409
        if (this.editMode || this.cellTemplate || this.errorShowing) {
1,224,566✔
410
            return '';
5,453✔
411
        }
412

413
        if (this.formatter) {
1,219,113✔
414
            return this.formatter(this.value, this.rowData, this.columnData);
22,150✔
415
        }
416

417
        const args = this.column.pipeArgs;
1,196,963✔
418
        const locale = this.grid.locale;
1,196,963✔
419

420
        switch (this.column.dataType) {
1,196,963✔
421
            case GridColumnDataType.Percent:
422
                return formatPercent(this.value, locale, args.digitsInfo);
427✔
423
            case GridColumnDataType.Currency:
424
                return formatCurrency(this.value, this.currencyCode, args.display, args.digitsInfo, locale);
20,356✔
425
            case GridColumnDataType.Date:
426
            case GridColumnDataType.DateTime:
427
            case GridColumnDataType.Time:
428
                return formatDate(this.value, args.format, locale, args.timezone);
172,109✔
429
        }
430
        return this.value;
1,004,071✔
431
    }
432

433
    @HostBinding('class.igx-grid__td--bool-true')
434
    public get booleanClass() {
435
        return this.column.dataType === 'boolean' && this.value;
1,224,553✔
436
    }
437

438
    /**
439
     * Returns a reference to the nativeElement of the cell.
440
     * ```typescript
441
     * let cellNativeElement = this.cell.nativeElement;
442
     * ```
443
     *
444
     * @memberof IgxGridCellComponent
445
     */
446
    public get nativeElement(): HTMLElement {
447
        return this.element.nativeElement;
1,353,534✔
448
    }
449

450
    /**
451
     * @hidden
452
     * @internal
453
     */
454
    @Input()
455
    public get cellSelectionMode() {
456
        return this._cellSelection;
312,096✔
457
    }
458

459
    public set cellSelectionMode(value) {
460
        if (this._cellSelection === value) {
155,889✔
461
            return;
154,917✔
462
        }
463
        this.zone.runOutsideAngular(() => {
972✔
464
            if (value === GridSelectionMode.multiple) {
972✔
465
                this.addPointerListeners(value);
60✔
466
            } else {
467
                this.removePointerListeners(this._cellSelection);
912✔
468
            }
469
        });
470
        this._cellSelection = value;
972✔
471
    }
472

473
    /**
474
     * @hidden
475
     * @internal
476
     */
477
    @Input()
478
    public set lastSearchInfo(value: ISearchInfo) {
479
        this._lastSearchInfo = value;
161,001✔
480
        this.highlightText(this._lastSearchInfo.searchText, this._lastSearchInfo.caseSensitive, this._lastSearchInfo.exactMatch);
161,001✔
481
    }
482

483
    /**
484
     * @hidden
485
     * @internal
486
     */
487
    @Input()
488
    @HostBinding('class.igx-grid__td--pinned-last')
489
    public lastPinned = false;
155,521✔
490

491
    /**
492
     * @hidden
493
     * @internal
494
     */
495
    @Input()
496
    @HostBinding('class.igx-grid__td--pinned-first')
497
    public firstPinned = false;
155,521✔
498

499
    /**
500
     * Returns whether the cell is in edit mode.
501
     */
502
    @Input({ transform: booleanAttribute })
503
    @HostBinding('class.igx-grid__td--editing')
504
    public editMode = false;
155,521✔
505

506
    /**
507
     * Sets/get the `role` property of the cell.
508
     * Default value is `"gridcell"`.
509
     * ```typescript
510
     * this.cell.role = 'grid-cell';
511
     * ```
512
     * ```typescript
513
     * let cellRole = this.cell.role;
514
     * ```
515
     *
516
     * @memberof IgxGridCellComponent
517
     */
518
    @HostBinding('attr.role')
519
    public role = 'gridcell';
155,521✔
520

521
    /**
522
     * Gets whether the cell is editable.
523
     * ```typescript
524
     * let isCellReadonly = this.cell.readonly;
525
     * ```
526
     *
527
     * @memberof IgxGridCellComponent
528
     */
529
    @HostBinding('attr.aria-readonly')
530
    public get readonly(): boolean {
531
        return !this.editable;
1,224,554✔
532
    }
533

534
    /** @hidden @internal */
535
    @HostBinding('attr.aria-describedby')
536
    public get ariaDescribeBy() {
537
        return this.isInvalid ? this.ariaErrorMessage : null;
1,225,648✔
538
    }
539

540
    /** @hidden @internal */
541
    public get ariaErrorMessage() {
542
        return this.grid.id + '_' + this.column.field + '_' + this.intRow.index + '_error';
253✔
543
    }
544

545
    /**
546
     * @hidden
547
     * @internal
548
     */
549
    @HostBinding('class.igx-grid__td--invalid')
550
    @HostBinding('attr.aria-invalid')
551
    public get isInvalid() {
552
        if (this.formGroup) {
4,664,011✔
553
            const isInvalid = this.grid.validation?.isFieldInvalid(this.formGroup, this.column?.field);
61,295✔
554
            return !this.intRow.deleted && isInvalid;
61,295✔
555
        }
556
        return false;
4,602,716✔
557
    }
558

559
    /**
560
     * @hidden
561
     * @internal
562
     */
563
    @HostBinding('class.igx-grid__td--valid')
564
    public get isValidAfterEdit() {
565
        if (this.formGroup) {
1,224,553✔
566
            const isValidAfterEdit = this.grid.validation?.isFieldValidAfterEdit(this.formGroup, this.column?.field);
11,120✔
567
            return this.editMode && isValidAfterEdit;
11,120✔
568
        }
569
        return false;
1,213,433✔
570
    }
571

572
    /**
573
     * Gets the formControl responsible for value changes and validation for this cell.
574
     */
575
    protected get formControl(): FormControl {
576
        return this.grid.validation.getFormControl(this.intRow.key, this.column.field) as FormControl;
54,599✔
577
    }
578

579
    public get gridRowSpan(): number {
580
        return this.column.gridRowSpan;
198✔
581
    }
582

583
    public get gridColumnSpan(): number {
584
        return this.column.gridColumnSpan;
198✔
585
    }
586

587
    public get rowEnd(): number {
588
        return this.column.rowEnd;
×
589
    }
590

591
    public get colEnd(): number {
592
        return this.column.colEnd;
×
593
    }
594

595
    public get rowStart(): number {
596
        return this.column.rowStart;
×
597
    }
598

599
    public get colStart(): number {
600
        return this.column.colStart;
×
601
    }
602

603
    /**
604
     * Gets the width of the cell.
605
     * ```typescript
606
     * let cellWidth = this.cell.width;
607
     * ```
608
     *
609
     * @memberof IgxGridCellComponent
610
     */
611
    @Input()
612
    public width = '';
155,521✔
613

614
    /**
615
     * @hidden
616
     */
617
    @Input()
618
    @HostBinding('class.igx-grid__td--active')
619
    public active = false;
155,521✔
620

621
    @HostBinding('attr.aria-selected')
622
    public get ariaSelected() {
623
        return this.selected || this.column.selected || this.intRow.selected;
1,224,553✔
624
    }
625

626
    /**
627
     * Gets whether the cell is selected.
628
     * ```typescript
629
     * let isSelected = this.cell.selected;
630
     * ```
631
     *
632
     * @memberof IgxGridCellComponent
633
     */
634
    @HostBinding('class.igx-grid__td--selected')
635
    public get selected() {
636
        return this.selectionService.selected(this.selectionNode);
2,451,192✔
637
    }
638

639
    /**
640
     * Selects/deselects the cell.
641
     * ```typescript
642
     * this.cell.selected = true.
643
     * ```
644
     *
645
     * @memberof IgxGridCellComponent
646
     */
647
    public set selected(val: boolean) {
648
        const node = this.selectionNode;
1✔
649
        if (val) {
1!
650
            this.selectionService.add(node);
1✔
651
        } else {
652
            this.selectionService.remove(node);
×
653
        }
654
        this.grid.notifyChanges();
1✔
655
    }
656

657
    /**
658
     * Gets whether the cell column is selected.
659
     * ```typescript
660
     * let isCellColumnSelected = this.cell.columnSelected;
661
     * ```
662
     *
663
     * @memberof IgxGridCellComponent
664
     */
665
    @HostBinding('class.igx-grid__td--column-selected')
666
    public get columnSelected() {
667
        return this.selectionService.isColumnSelected(this.column.field);
1,224,553✔
668
    }
669

670
    /**
671
     * Sets the current edit value while a cell is in edit mode.
672
     * Only for cell editing mode.
673
     * ```typescript
674
     * this.cell.editValue = value;
675
     * ```
676
     *
677
     * @memberof IgxGridCellComponent
678
     */
679
    public set editValue(value) {
680
        if (this.grid.crudService.cellInEditMode) {
44✔
681
            this.grid.crudService.cell.editValue = value;
44✔
682
        }
683
    }
684

685
    /**
686
     * Gets the current edit value while a cell is in edit mode.
687
     * Only for cell editing mode.
688
     * ```typescript
689
     * let editValue = this.cell.editValue;
690
     * ```
691
     *
692
     * @memberof IgxGridCellComponent
693
     */
694
    public get editValue() {
695
        if (this.grid.crudService.cellInEditMode) {
163✔
696
            return this.grid.crudService.cell.editValue;
163✔
697
        }
698
    }
699

700
    /**
701
     * Returns whether the cell is editable.
702
     */
703
    public get editable(): boolean {
704
        return this.column.editable && !this.intRow.disabled;
1,225,837✔
705
    }
706

707
    /**
708
     * @hidden
709
     */
710
    @Input()
711
    @HostBinding('class.igx-grid__td--row-pinned-first')
712
    public displayPinnedChip = false;
155,521✔
713

714
    @HostBinding('style.min-height.px')
715
    protected get minHeight() {
716
        if ((this.grid as any).isCustomSetRowHeight) {
1,224,553✔
717
            return this.grid.renderedRowHeight;
464✔
718
        }
719
    }
720

721
    @HostBinding('attr.aria-rowindex')
722
    protected get ariaRowIndex(): number {
723
        // +2 because aria-rowindex is 1-based and the first row is the header
724
        return this.rowIndex + 2;
1,224,553✔
725
    }
726

727
    @HostBinding('attr.aria-colindex')
728
    protected get ariaColIndex(): number {
729
        return this.column.index + 1;
1,224,553✔
730
    }
731

732
    @ViewChild('defaultCell', { read: TemplateRef, static: true })
733
    protected defaultCellTemplate: TemplateRef<any>;
734

735
    @ViewChild('emptyCell', { read: TemplateRef, static: true })
736
    protected emptyCellTemplate: TemplateRef<any>;
737

738
    @ViewChild('defaultPinnedIndicator', { read: TemplateRef, static: true })
739
    protected defaultPinnedIndicator: TemplateRef<any>;
740

741
    @ViewChild('inlineEditor', { read: TemplateRef, static: true })
742
    protected inlineEditorTemplate: TemplateRef<any>;
743

744
    @ViewChild('addRowCell', { read: TemplateRef, static: true })
745
    protected addRowCellTemplate: TemplateRef<any>;
746

747
    @ViewChild(IgxTextHighlightDirective, { read: IgxTextHighlightDirective })
748
    protected set highlight(value: IgxTextHighlightDirective) {
749
        this._highlight = value;
157,674✔
750

751
        if (this._highlight && this.grid.lastSearchInfo.searchText) {
157,674✔
752
            this._highlight.highlight(this.grid.lastSearchInfo.searchText,
400✔
753
                this.grid.lastSearchInfo.caseSensitive,
754
                this.grid.lastSearchInfo.exactMatch);
755
            this._highlight.activateIfNecessary();
400✔
756
        }
757
    }
758

759
    protected get highlight() {
760
        return this._highlight;
357,308✔
761
    }
762

763
    protected get selectionNode(): ISelectionNode {
764
        return {
2,453,102✔
765
            row: this.rowIndex,
766
            column: this.column.columnLayoutChild ? this.column.parent.visibleIndex : this.visibleColumnIndex,
2,453,102✔
767
            layout: this.column.columnLayoutChild ? {
2,453,102✔
768
                rowStart: this.column.rowStart,
769
                colStart: this.column.colStart,
770
                rowEnd: this.column.rowEnd,
771
                colEnd: this.column.colEnd,
772
                columnVisibleIndex: this.visibleColumnIndex
773
            } : null
774
        };
775
    }
776

777
    /**
778
     * Sets/gets the highlight class of the cell.
779
     * Default value is `"igx-highlight"`.
780
     * ```typescript
781
     * let highlightClass = this.cell.highlightClass;
782
     * ```
783
     * ```typescript
784
     * this.cell.highlightClass = 'igx-cell-highlight';
785
     * ```
786
     *
787
     * @memberof IgxGridCellComponent
788
     */
789
    public highlightClass = 'igx-highlight';
155,521✔
790

791
    /**
792
     * Sets/gets the active highlight class class of the cell.
793
     * Default value is `"igx-highlight__active"`.
794
     * ```typescript
795
     * let activeHighlightClass = this.cell.activeHighlightClass;
796
     * ```
797
     * ```typescript
798
     * this.cell.activeHighlightClass = 'igx-cell-highlight_active';
799
     * ```
800
     *
801
     * @memberof IgxGridCellComponent
802
     */
803
    public activeHighlightClass = 'igx-highlight__active';
155,521✔
804

805
    /** @hidden @internal */
806
    public get step(): number {
807
        const digitsInfo = this.column.pipeArgs.digitsInfo;
214✔
808
        if (!digitsInfo) {
214!
809
            return 1;
×
810
        }
811
        const step = +digitsInfo.substr(digitsInfo.indexOf('.') + 1, 1);
214✔
812
        return 1 / (Math.pow(10, step));
214✔
813
    }
814

815
    /** @hidden @internal */
816
    public get currencyCode(): string {
817
        return this.column.pipeArgs.currencyCode ?
57,300✔
818
            this.column.pipeArgs.currencyCode : getLocaleCurrencyCode(this.grid.locale);
819
    }
820

821
    /** @hidden @internal */
822
    public get currencyCodeSymbol(): string {
823
        return getCurrencySymbol(this.currencyCode, 'wide', this.grid.locale);
4✔
824
    }
825

826
    protected _lastSearchInfo: ISearchInfo;
827
    private _highlight: IgxTextHighlightDirective;
828
    private _cellSelection: GridSelectionMode = GridSelectionMode.multiple;
155,521✔
829
    private _vIndex = -1;
155,521✔
830

831
    constructor(
832
        protected selectionService: IgxGridSelectionService,
155,521✔
833
        @Inject(IGX_GRID_BASE) public grid: GridType,
155,521✔
834
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
155,521✔
835
        public cdr: ChangeDetectorRef,
155,521✔
836
        private element: ElementRef<HTMLElement>,
155,521✔
837
        protected zone: NgZone,
155,521✔
838
        private touchManager: HammerGesturesManager,
155,521✔
839
        protected platformUtil: PlatformUtil
155,521✔
840
    ) { }
841

842
    /**
843
     * @hidden
844
     * @internal
845
     */
846
    @HostListener('dblclick', ['$event'])
847
    public onDoubleClick = (event: MouseEvent) => {
155,521✔
848
        if (event.type === 'doubletap') {
153✔
849
            // prevent double-tap to zoom on iOS
850
            event.preventDefault();
1✔
851
        }
852
        if (this.editable && !this.editMode && !this.intRow.deleted && !this.grid.crudService.rowEditingBlocked) {
153✔
853
            this.grid.crudService.enterEditMode(this, event as Event);
147✔
854
        }
855

856
        this.grid.doubleClick.emit({
153✔
857
            cell: this.getCellType(),
858
            event
859
        });
860
    };
861

862
    /**
863
     * @hidden
864
     * @internal
865
     */
866
    @HostListener('click', ['$event'])
867
    public onClick(event: MouseEvent) {
868
        this.grid.cellClick.emit({
265✔
869
            cell: this.getCellType(),
870
            event
871
        });
872
    }
873

874
    /**
875
     * @hidden
876
     * @internal
877
     */
878
    public ngOnInit() {
879
        this.zone.runOutsideAngular(() => {
155,521✔
880
            this.nativeElement.addEventListener('pointerdown', this.pointerdown);
155,521✔
881
            this.addPointerListeners(this.cellSelectionMode);
155,521✔
882
        });
883
        if (this.platformUtil.isIOS) {
155,521✔
884
            this.touchManager.addEventListener(this.nativeElement, 'doubletap', this.onDoubleClick, {
40✔
885
                cssProps: {} /* don't disable user-select, etc */
886
            });
887
        }
888

889
    }
890

891
    public ngAfterViewInit() {
892
        this.errorTooltip.changes.pipe(takeUntil(this._destroy$)).subscribe(() => {
155,521✔
893
            if (this.errorTooltip.length > 0 && this.active) {
44✔
894
                // error ocurred
895
                this.cdr.detectChanges();
25✔
896
                this.openErrorTooltip();
25✔
897
            }
898
        });
899
    }
900

901
    /**
902
     * @hidden
903
     * @internal
904
     */
905
    public errorShowing = false;
155,521✔
906

907
    private openErrorTooltip() {
908
        const tooltip = this.errorTooltip.first;
26✔
909
        tooltip.open(
26✔
910
            {
911
                target: this.errorIcon.el.nativeElement,
912
                closeOnOutsideClick: true,
913
                excludeFromOutsideClick: [this.nativeElement],
914
                closeOnEscape: false,
915
                outlet: this.grid.outlet,
916
                modal: false,
917
                positionStrategy: new AutoPositionStrategy({
918
                    horizontalStartPoint: HorizontalAlignment.Center,
919
                    horizontalDirection: HorizontalAlignment.Center,
920
                    openAnimation: useAnimation(scaleInCenter, { params: { duration: '150ms' } }),
921
                    closeAnimation: useAnimation(fadeOut, { params: { duration: '75ms' } })
922
                })
923
            }
924
        );
925
    }
926

927
    /**
928
     * @hidden
929
     * @internal
930
     */
931
    public ngOnDestroy() {
932
        this.zone.runOutsideAngular(() => {
154,176✔
933
            this.nativeElement.removeEventListener('pointerdown', this.pointerdown);
154,176✔
934
            this.removePointerListeners(this.cellSelectionMode);
154,176✔
935
        });
936
        this.touchManager.destroy();
154,176✔
937
        this._destroy$.next();
154,176✔
938
        this._destroy$.complete();
154,176✔
939
    }
940

941
    /**
942
     * @hidden
943
     * @internal
944
     */
945
    public ngOnChanges(changes: SimpleChanges): void {
946
        if (changes.editMode && changes.editMode.currentValue && this.formControl) {
295,251✔
947
            // ensure when values change, form control is forced to be marked as touche.
948
            this.formControl.valueChanges.pipe(takeWhile(() => this.editMode)).subscribe(() => this.formControl.markAsTouched());
402✔
949
            // while in edit mode subscribe to value changes on the current form control and set to editValue
950
            this.formControl.statusChanges.pipe(takeWhile(() => this.editMode)).subscribe(status => {
402✔
951
                if (status === 'INVALID' && this.errorTooltip.length > 0) {
192!
952
                    this.cdr.detectChanges();
×
953
                    const tooltip = this.errorTooltip.first;
×
954
                    this.resizeAndRepositionOverlayById(tooltip.overlayId, this.errorTooltip.first.element.offsetWidth);
×
955
                }
956
            });
957
        }
958
        if (changes.value && !changes.value.firstChange) {
295,251✔
959
            if (this.highlight) {
47,941✔
960
                this.highlight.lastSearchInfo.searchText = this.grid.lastSearchInfo.searchText;
45,525✔
961
                this.highlight.lastSearchInfo.caseSensitive = this.grid.lastSearchInfo.caseSensitive;
45,525✔
962
                this.highlight.lastSearchInfo.exactMatch = this.grid.lastSearchInfo.exactMatch;
45,525✔
963
            }
964
            const isInEdit = this.grid.rowEditable ? this.row.inEditMode : this.editMode;
47,941✔
965
            if (this.formControl && this.formControl.value !== changes.value.currentValue && !isInEdit) {
47,941✔
966
                this.formControl.setValue(changes.value.currentValue);
131✔
967
            }
968
        }
969
    }
970

971

972

973
    /**
974
     * @hidden @internal
975
     */
976
    private resizeAndRepositionOverlayById(overlayId: string, newSize: number) {
977
        const overlay = this.overlayService.getOverlayById(overlayId);
×
978
        if (!overlay) return;
×
979
        overlay.initialSize.width = newSize;
×
980
        overlay.elementRef.nativeElement.parentElement.style.width = newSize + 'px';
×
981
        this.overlayService.reposition(overlayId);
×
982
    }
983

984
    /**
985
     * Starts/ends edit mode for the cell.
986
     *
987
     * ```typescript
988
     * cell.setEditMode(true);
989
     * ```
990
     */
991
    public setEditMode(value: boolean): void {
992
        if (this.intRow.deleted) {
30!
993
            return;
×
994
        }
995
        if (this.editable && value) {
30✔
996
            if (this.grid.crudService.cellInEditMode) {
26✔
997
                this.grid.gridAPI.update_cell(this.grid.crudService.cell);
5✔
998
                this.grid.crudService.endCellEdit();
5✔
999
            }
1000
            this.grid.crudService.enterEditMode(this);
26✔
1001
        } else {
1002
            this.grid.crudService.endCellEdit();
4✔
1003
        }
1004
        this.grid.notifyChanges();
30✔
1005
    }
1006

1007
    /**
1008
     * Sets new value to the cell.
1009
     * ```typescript
1010
     * this.cell.update('New Value');
1011
     * ```
1012
     *
1013
     * @memberof IgxGridCellComponent
1014
     */
1015
    // TODO: Refactor
1016
    public update(val: any) {
1017
        if (this.intRow.deleted) {
34!
1018
            return;
×
1019
        }
1020

1021
        let cell = this.grid.crudService.cell;
34✔
1022
        if (!cell) {
34✔
1023
            cell = this.grid.crudService.createCell(this);
14✔
1024
        }
1025
        cell.editValue = val;
34✔
1026
        this.grid.gridAPI.update_cell(cell);
34✔
1027
        this.grid.crudService.endCellEdit();
34✔
1028
        this.cdr.markForCheck();
34✔
1029
    }
1030

1031
    /**
1032
     *
1033
     * @hidden
1034
     * @internal
1035
     */
1036
    public pointerdown = (event: PointerEvent) => {
155,521✔
1037

1038
        if (this.isMerged) {
433✔
1039
            // need an approximation of where in the cell the user clicked to get actual index to be activated.
1040
            const scrollOffset = this.grid.verticalScrollContainer.scrollPosition + (event.y - this.grid.tbody.nativeElement.getBoundingClientRect().y);
5✔
1041
            const targetRowIndex = this.grid.verticalScrollContainer.getIndexAtScroll(scrollOffset);
5✔
1042
            if (targetRowIndex != this.rowIndex) {
5!
NEW
1043
                const row = this.grid.rowList.toArray().find(x => x.index === targetRowIndex);
×
NEW
1044
                const actualTarget = row.cells.find(x => x.column === this.column);
×
NEW
1045
                actualTarget.pointerdown(event);
×
NEW
1046
                return;
×
1047
            }
1048
        }
1049

1050
        if (this.cellSelectionMode !== GridSelectionMode.multiple) {
433✔
1051
            this.activate(event);
23✔
1052
            return;
23✔
1053
        }
1054
        if (!this.platformUtil.isLeftClick(event)) {
410✔
1055
            event.preventDefault();
4✔
1056
            this.grid.navigation.setActiveNode({ rowIndex: this.rowIndex, colIndex: this.visibleColumnIndex });
4✔
1057
            this.selectionService.addKeyboardRange();
4✔
1058
            this.selectionService.initKeyboardState();
4✔
1059
            this.selectionService.primaryButton = false;
4✔
1060
            // Ensure RMB Click on edited cell does not end cell editing
1061
            if (!this.selected) {
4✔
1062
                this.grid.crudService.updateCell(true, event);
2✔
1063
            }
1064
            return;
4✔
1065
        } else {
1066
            this.selectionService.primaryButton = true;
406✔
1067
        }
1068
        this.selectionService.pointerDown(this.selectionNode, event.shiftKey, event.ctrlKey);
406✔
1069
        this.activate(event);
406✔
1070
    };
1071

1072
    /**
1073
     *
1074
     * @hidden
1075
     * @internal
1076
     */
1077
    public pointerenter = (event: PointerEvent) => {
155,521✔
1078
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
119✔
1079
        if (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID || this.grid.navigation.activeNode.gridID !== this.gridID)) {
119✔
1080
            return;
3✔
1081
        }
1082
        const dragMode = this.selectionService.pointerEnter(this.selectionNode, event);
116✔
1083
        if (dragMode) {
116✔
1084
            this.grid.cdr.detectChanges();
115✔
1085
        }
1086
    };
1087

1088
    /**
1089
     * @hidden
1090
     * @internal
1091
     */
1092
    public focusout = () => {
155,521✔
1093
        this.closeErrorTooltip();
49✔
1094
    }
1095

1096
    private closeErrorTooltip() {
1097
        const tooltip = this.errorTooltip.first;
49✔
1098
        if (tooltip) {
49!
1099
            tooltip.close();
×
1100
        }
1101
    }
1102

1103
    /**
1104
     * @hidden
1105
     * @internal
1106
     */
1107
    public pointerup = (event: PointerEvent) => {
155,521✔
1108
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
407✔
1109
        if (!this.platformUtil.isLeftClick(event) || (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID ||
407✔
1110
            this.grid.navigation.activeNode.gridID !== this.gridID))) {
1111
            return;
4✔
1112
        }
1113
        if (this.selectionService.pointerUp(this.selectionNode, this.grid.rangeSelected)) {
403✔
1114
            this.grid.cdr.detectChanges();
50✔
1115
        }
1116
    };
1117

1118
    /**
1119
     * @hidden
1120
     * @internal
1121
     */
1122
    public activate(event: FocusEvent | KeyboardEvent) {
1123
        const node = this.selectionNode;
984✔
1124
        let shouldEmitSelection = !this.selectionService.isActiveNode(node);
984✔
1125

1126
        if (this.selectionService.primaryButton) {
984!
1127
            const currentActive = this.selectionService.activeElement;
984✔
1128
            if (this.cellSelectionMode === GridSelectionMode.single && (event as any)?.ctrlKey && this.selected) {
984✔
1129
                this.selectionService.activeElement = null;
1✔
1130
                shouldEmitSelection = true;
1✔
1131
            } else {
1132
                this.selectionService.activeElement = node;
983✔
1133
            }
1134
            const cancel = this._updateCRUDStatus(event);
984✔
1135
            if (cancel) {
984✔
1136
                this.selectionService.activeElement = currentActive;
2✔
1137
                return;
2✔
1138
            }
1139

1140
            const activeElement = this.selectionService.activeElement;
982✔
1141
            const row = activeElement ? this.grid.gridAPI.get_row_by_index(activeElement.row) : null;
982✔
1142
            if (this.grid.crudService.rowEditingBlocked && row && this.intRow.key !== row.key) {
982!
1143
                return;
×
1144
            }
1145

1146
        } else {
1147
            this.selectionService.activeElement = null;
×
1148
            if (this.grid.crudService.cellInEditMode && !this.editMode) {
×
1149
                this.grid.crudService.updateCell(true, event);
×
1150
            }
1151
        }
1152

1153
        this.grid.navigation.setActiveNode({ row: this.rowIndex, column: this.visibleColumnIndex });
982✔
1154

1155
        const isTargetErrorIcon = event && event.target && event.target === this.errorIcon?.el.nativeElement
982✔
1156
        if (this.isInvalid && !isTargetErrorIcon) {
982✔
1157
            this.cdr.detectChanges();
1✔
1158
            this.openErrorTooltip();
1✔
1159
            this.grid.activeNodeChange.pipe(first()).subscribe(() => {
1✔
1160
                this.closeErrorTooltip();
×
1161
            });
1162
        }
1163
        this.selectionService.primaryButton = true;
982✔
1164
        if (this.cellSelectionMode === GridSelectionMode.multiple && this.selectionService.activeElement) {
982✔
1165
            if (this.selectionService.isInMap(this.selectionService.activeElement) && (event as any)?.ctrlKey && !(event as any)?.shiftKey) {
950✔
1166
                this.selectionService.remove(this.selectionService.activeElement);
3✔
1167
                shouldEmitSelection = true;
3✔
1168
            } else {
1169
                this.selectionService.add(this.selectionService.activeElement, false); // pointer events handle range generation
947✔
1170
                this.selectionService.keyboardStateOnFocus(node, this.grid.rangeSelected, this.nativeElement);
947✔
1171
            }
1172
        }
1173
        if (this.grid.isCellSelectable && shouldEmitSelection) {
982✔
1174
            this.zone.run(() => this.grid.selected.emit({ cell: this.getCellType(), event }));
951✔
1175
        }
1176
    }
1177

1178
    /**
1179
     * If the provided string matches the text in the cell, the text gets highlighted.
1180
     * ```typescript
1181
     * this.cell.highlightText('Cell Value', true);
1182
     * ```
1183
     *
1184
     * @memberof IgxGridCellComponent
1185
     */
1186
    public highlightText(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
1187
        return this.highlight && this.column.searchable ? this.highlight.highlight(text, caseSensitive, exactMatch) : 0;
164,188✔
1188
    }
1189

1190
    /**
1191
     * Clears the highlight of the text in the cell.
1192
     * ```typescript
1193
     * this.cell.clearHighLight();
1194
     * ```
1195
     *
1196
     * @memberof IgxGridCellComponent
1197
     */
1198
    public clearHighlight() {
1199
        if (this.highlight && this.column.searchable) {
40✔
1200
            this.highlight.clearHighlight();
40✔
1201
        }
1202
    }
1203

1204
    /**
1205
     * @hidden
1206
     * @internal
1207
     */
1208
    public calculateSizeToFit(range: any): number {
1209
        return this.platformUtil.getNodeSizeViaRange(range, this.nativeElement);
214✔
1210
    }
1211

1212
    /**
1213
     * @hidden
1214
     * @internal
1215
     */
1216
    public get searchMetadata() {
1217
        const meta = new Map<string, any>();
306,972✔
1218
        meta.set('pinned', this.grid.isRecordPinnedByViewIndex(this.intRow.index));
306,972✔
1219
        return meta;
306,972✔
1220
    }
1221

1222
    /**
1223
     * @hidden
1224
     * @internal
1225
     */
1226
    private _updateCRUDStatus(event?: Event) {
1227
        if (this.editMode) {
984✔
1228
            return;
48✔
1229
        }
1230

1231
        let editableArgs;
1232
        const crud = this.grid.crudService;
936✔
1233
        const editableCell = this.grid.crudService.cell;
936✔
1234
        const editMode = !!(crud.row || crud.cell);
936✔
1235

1236
        if (this.editable && editMode && !this.intRow.deleted) {
936✔
1237
            if (editableCell) {
88✔
1238
                editableArgs = this.grid.crudService.updateCell(false, event);
77✔
1239

1240
                /* This check is related with the following issue #6517:
1241
                 * when edit cell that belongs to a column which is sorted and press tab,
1242
                 * the next cell in edit mode is with wrong value /its context is not updated/;
1243
                 * So we reapply sorting before the next cell enters edit mode.
1244
                 * Also we need to keep the notifyChanges below, because of the current
1245
                 * change detection cycle when we have editing with enabled transactions
1246
                 */
1247
                if (this.grid.sortingExpressions.length && this.grid.sortingExpressions.indexOf(editableCell.column.field)) {
77!
1248
                    this.grid.cdr.detectChanges();
×
1249
                }
1250

1251
                if (editableArgs && editableArgs.cancel) {
77✔
1252
                    return true;
2✔
1253
                }
1254

1255
                crud.exitCellEdit(event);
75✔
1256
            }
1257
            this.grid.tbody.nativeElement.focus({ preventScroll: true });
86✔
1258
            this.grid.notifyChanges();
86✔
1259
            crud.enterEditMode(this, event);
86✔
1260
            return false;
86✔
1261
        }
1262

1263
        if (editableCell && crud.sameRow(this.cellID.rowID)) {
848✔
1264
            this.grid.crudService.updateCell(true, event);
1✔
1265
        } else if (editMode && !crud.sameRow(this.cellID.rowID)) {
847✔
1266
            this.grid.crudService.endEdit(true, event);
3✔
1267
        }
1268
    }
1269

1270
    private addPointerListeners(selection) {
1271
        if (selection !== GridSelectionMode.multiple) {
155,581✔
1272
            return;
604✔
1273
        }
1274
        this.nativeElement.addEventListener('pointerenter', this.pointerenter);
154,977✔
1275
        this.nativeElement.addEventListener('pointerup', this.pointerup);
154,977✔
1276
        this.nativeElement.addEventListener('focusout', this.focusout);
154,977✔
1277
    }
1278

1279
    private removePointerListeners(selection) {
1280
        if (selection !== GridSelectionMode.multiple) {
155,088✔
1281
            return;
852✔
1282
        }
1283
        this.nativeElement.removeEventListener('pointerenter', this.pointerenter);
154,236✔
1284
        this.nativeElement.removeEventListener('pointerup', this.pointerup);
154,236✔
1285
        this.nativeElement.removeEventListener('focusout', this.focusout);
154,236✔
1286
    }
1287

1288
    private getCellType(useRow?: boolean): CellType {
1289
        const rowID = useRow ? this.grid.createRow(this.intRow.index, this.intRow.data) : this.intRow.index;
3,394✔
1290
        return new IgxGridCell(this.grid, rowID, this.column);
3,394✔
1291
    }
1292
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc