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

IgniteUI / igniteui-angular / 16193550997

10 Jul 2025 11:12AM UTC coverage: 4.657% (-87.0%) from 91.64%
16193550997

Pull #16028

github

web-flow
Merge f7a9963b8 into 87246e3ce
Pull Request #16028: fix(radio-group): dynamically added radio buttons do not initialize

178 of 15764 branches covered (1.13%)

18 of 19 new or added lines in 2 files covered. (94.74%)

25721 existing lines in 324 files now uncovered.

1377 of 29570 relevant lines covered (4.66%)

0.53 hits per line

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

0.34
/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✔
UNCOV
105
    private _destroy$ = new Subject<void>();
×
106
    /**
107
     * @hidden
108
     * @internal
109
     */
110
    @HostBinding('class.igx-grid__td--new')
111
    public get isEmptyAddRowCell() {
UNCOV
112
        return this.intRow.addRowUI && (this.value === undefined || this.value === null);
×
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
    /**
149
     * @hidden
150
     * @internal
151
     */
152
    protected get formGroup(): FormGroup {
UNCOV
153
        return this.grid.validation.getFormGroup(this.intRow.key);
×
154
    }
155

156
    /**
157
     * @hidden
158
     * @internal
159
     */
160
    @Input()
161
    public intRow: IgxRowDirective;
162

163
    /**
164
     * Gets the row of the cell.
165
     * ```typescript
166
     * let cellRow = this.cell.row;
167
     * ```
168
     *
169
     * @memberof IgxGridCellComponent
170
     */
171
    @Input()
172
    public get row(): RowType {
UNCOV
173
        return this.grid.createRow(this.intRow.index);
×
174
    }
175

176
    /**
177
     * Gets the data of the row of the cell.
178
     * ```typescript
179
     * let rowData = this.cell.rowData;
180
     * ```
181
     *
182
     * @memberof IgxGridCellComponent
183
     */
184
    @Input()
185
    public rowData: any;
186

187
    /**
188
     * @hidden
189
     * @internal
190
     */
191
    @Input()
192
    public columnData: any;
193

194
    /**
195
     * Sets/gets the template of the cell.
196
     * ```html
197
     * <ng-template #cellTemplate igxCell let-value>
198
     *   <div style="font-style: oblique; color:blueviolet; background:red">
199
     *       <span>{{value}}</span>
200
     *   </div>
201
     * </ng-template>
202
     * ```
203
     * ```typescript
204
     * @ViewChild('cellTemplate',{read: TemplateRef})
205
     * cellTemplate: TemplateRef<any>;
206
     * ```
207
     * ```typescript
208
     * this.cell.cellTemplate = this.cellTemplate;
209
     * ```
210
     * ```typescript
211
     * let template =  this.cell.cellTemplate;
212
     * ```
213
     *
214
     * @memberof IgxGridCellComponent
215
     */
216
    @Input()
217
    public cellTemplate: TemplateRef<any>;
218

219
    @Input()
220
    public cellValidationErrorTemplate: TemplateRef<any>;
221

222
    @Input()
223
    public pinnedIndicator: TemplateRef<any>;
224

225
    /**
226
     * Sets/gets the cell value.
227
     * ```typescript
228
     * this.cell.value = "Cell Value";
229
     * ```
230
     * ```typescript
231
     * let cellValue = this.cell.value;
232
     * ```
233
     *
234
     * @memberof IgxGridCellComponent
235
     */
236
    @Input()
237
    public value: any;
238

239
    /**
240
     * Gets the cell formatter.
241
     * ```typescript
242
     * let cellForamatter = this.cell.formatter;
243
     * ```
244
     *
245
     * @memberof IgxGridCellComponent
246
     */
247
    @Input()
248
    public formatter: (value: any, rowData?: any, columnData?: any) => any;
249

250
    /**
251
     * Gets the cell template context object.
252
     * ```typescript
253
     *  let context = this.cell.context();
254
     * ```
255
     *
256
     * @memberof IgxGridCellComponent
257
     */
258
    public get context(): IgxCellTemplateContext {
UNCOV
259
        const getCellType = () => this.getCellType(true);
×
UNCOV
260
        const ctx: IgxCellTemplateContext = {
×
261
            $implicit: this.value,
262
            additionalTemplateContext: this.column.additionalTemplateContext,
263
            get cell() {
264
                /* Turns the `cell` property from the template context object into lazy-evaluated one.
265
                 * Otherwise on each detection cycle the cell template is recreating N cell instances where
266
                 * N = number of visible cells in the grid, leading to massive performance degradation in large grids.
267
                 */
UNCOV
268
                return getCellType();
×
269
            }
270
        };
UNCOV
271
        if (this.editMode) {
×
UNCOV
272
            ctx.formControl = this.formControl;
×
273
        }
UNCOV
274
        if (this.isInvalid) {
×
UNCOV
275
            ctx.defaultErrorTemplate = this.defaultErrorTemplate;
×
276
        }
UNCOV
277
        return ctx;
×
278
    }
279

280
    /**
281
     * Gets the cell template.
282
     * ```typescript
283
     * let template = this.cell.template;
284
     * ```
285
     *
286
     * @memberof IgxGridCellComponent
287
     */
288
    public get template(): TemplateRef<any> {
UNCOV
289
        if (this.editMode && this.formGroup) {
×
UNCOV
290
            const inlineEditorTemplate = this.column.inlineEditorTemplate;
×
UNCOV
291
            return inlineEditorTemplate ? inlineEditorTemplate : this.inlineEditorTemplate;
×
292
        }
UNCOV
293
        if (this.cellTemplate) {
×
UNCOV
294
            return this.cellTemplate;
×
295
        }
UNCOV
296
        if (this.grid.rowEditable && this.intRow.addRowUI) {
×
UNCOV
297
            return this.addRowCellTemplate;
×
298
        }
UNCOV
299
        return this.defaultCellTemplate;
×
300
    }
301

302
    /**
303
     * Gets the pinned indicator template.
304
     * ```typescript
305
     * let template = this.cell.pinnedIndicatorTemplate;
306
     * ```
307
     *
308
     * @memberof IgxGridCellComponent
309
     */
310
    public get pinnedIndicatorTemplate() {
UNCOV
311
        if (this.pinnedIndicator) {
×
312
            return this.pinnedIndicator;
×
313
        }
UNCOV
314
        return this.defaultPinnedIndicator;
×
315
    }
316

317
    /**
318
     * Gets the `id` of the grid in which the cell is stored.
319
     * ```typescript
320
     * let gridId = this.cell.gridID;
321
     * ```
322
     *
323
     * @memberof IgxGridCellComponent
324
     */
325
    public get gridID(): any {
UNCOV
326
        return this.intRow.gridID;
×
327
    }
328

329

330
    /**
331
     * Gets the `index` of the row where the cell is stored.
332
     * ```typescript
333
     * let rowIndex = this.cell.rowIndex;
334
     * ```
335
     *
336
     * @memberof IgxGridCellComponent
337
     */
338
    @HostBinding('attr.data-rowIndex')
339
    public get rowIndex(): number {
UNCOV
340
        return this.intRow.index;
×
341
    }
342

343
    /**
344
     * Gets the `index` of the cell column.
345
     * ```typescript
346
     * let columnIndex = this.cell.columnIndex;
347
     * ```
348
     *
349
     * @memberof IgxGridCellComponent
350
     */
351
    public get columnIndex(): number {
UNCOV
352
        return this.column.index;
×
353
    }
354

355
    /**
356
     * Returns the column visible index.
357
     * ```typescript
358
     * let visibleColumnIndex = this.cell.visibleColumnIndex;
359
     * ```
360
     *
361
     * @memberof IgxGridCellComponent
362
     */
363
    @HostBinding('attr.data-visibleIndex')
364
    @Input()
365
    public get visibleColumnIndex() {
UNCOV
366
        return this.column.columnLayoutChild ? this.column.visibleIndex : this._vIndex;
×
367
    }
368

369
    public set visibleColumnIndex(val) {
UNCOV
370
        this._vIndex = val;
×
371
    }
372

373
    /**
374
     * Gets the ID of the cell.
375
     * ```typescript
376
     * let cellID = this.cell.cellID;
377
     * ```
378
     *
379
     * @memberof IgxGridCellComponent
380
     */
381
    public get cellID() {
UNCOV
382
        const primaryKey = this.grid.primaryKey;
×
UNCOV
383
        const rowID = primaryKey ? this.rowData[primaryKey] : this.rowData;
×
UNCOV
384
        return { rowID, columnID: this.columnIndex, rowIndex: this.rowIndex };
×
385
    }
386

387
    @HostBinding('attr.id')
388
    public get attrCellID() {
UNCOV
389
        return `${this.intRow.gridID}_${this.rowIndex}_${this.visibleColumnIndex}`;
×
390
    }
391

392
    @HostBinding('attr.title')
393
    public get title() {
UNCOV
394
        if (this.editMode || this.cellTemplate || this.errorShowing) {
×
UNCOV
395
            return '';
×
396
        }
397

UNCOV
398
        if (this.formatter) {
×
UNCOV
399
            return this.formatter(this.value, this.rowData, this.columnData);
×
400
        }
401

UNCOV
402
        const args = this.column.pipeArgs;
×
UNCOV
403
        const locale = this.grid.locale;
×
404

UNCOV
405
        switch (this.column.dataType) {
×
406
            case GridColumnDataType.Percent:
UNCOV
407
                return formatPercent(this.value, locale, args.digitsInfo);
×
408
            case GridColumnDataType.Currency:
UNCOV
409
                return formatCurrency(this.value, this.currencyCode, args.display, args.digitsInfo, locale);
×
410
            case GridColumnDataType.Date:
411
            case GridColumnDataType.DateTime:
412
            case GridColumnDataType.Time:
UNCOV
413
                return formatDate(this.value, args.format, locale, args.timezone);
×
414
        }
UNCOV
415
        return this.value;
×
416
    }
417

418
    @HostBinding('class.igx-grid__td--bool-true')
419
    public get booleanClass() {
UNCOV
420
        return this.column.dataType === 'boolean' && this.value;
×
421
    }
422

423
    /**
424
     * Returns a reference to the nativeElement of the cell.
425
     * ```typescript
426
     * let cellNativeElement = this.cell.nativeElement;
427
     * ```
428
     *
429
     * @memberof IgxGridCellComponent
430
     */
431
    public get nativeElement(): HTMLElement {
UNCOV
432
        return this.element.nativeElement;
×
433
    }
434

435
    /**
436
     * @hidden
437
     * @internal
438
     */
439
    @Input()
440
    public get cellSelectionMode() {
UNCOV
441
        return this._cellSelection;
×
442
    }
443

444
    public set cellSelectionMode(value) {
UNCOV
445
        if (this._cellSelection === value) {
×
UNCOV
446
            return;
×
447
        }
UNCOV
448
        this.zone.runOutsideAngular(() => {
×
UNCOV
449
            if (value === GridSelectionMode.multiple) {
×
UNCOV
450
                this.addPointerListeners(value);
×
451
            } else {
UNCOV
452
                this.removePointerListeners(this._cellSelection);
×
453
            }
454
        });
UNCOV
455
        this._cellSelection = value;
×
456
    }
457

458
    /**
459
     * @hidden
460
     * @internal
461
     */
462
    @Input()
463
    public set lastSearchInfo(value: ISearchInfo) {
UNCOV
464
        this._lastSearchInfo = value;
×
UNCOV
465
        this.highlightText(this._lastSearchInfo.searchText, this._lastSearchInfo.caseSensitive, this._lastSearchInfo.exactMatch);
×
466
    }
467

468
    /**
469
     * @hidden
470
     * @internal
471
     */
472
    @Input()
473
    @HostBinding('class.igx-grid__td--pinned-last')
UNCOV
474
    public lastPinned = false;
×
475

476
    /**
477
     * @hidden
478
     * @internal
479
     */
480
    @Input()
481
    @HostBinding('class.igx-grid__td--pinned-first')
UNCOV
482
    public firstPinned = false;
×
483

484
    /**
485
     * Returns whether the cell is in edit mode.
486
     */
487
    @Input({ transform: booleanAttribute })
488
    @HostBinding('class.igx-grid__td--editing')
UNCOV
489
    public editMode = false;
×
490

491
    /**
492
     * Sets/get the `role` property of the cell.
493
     * Default value is `"gridcell"`.
494
     * ```typescript
495
     * this.cell.role = 'grid-cell';
496
     * ```
497
     * ```typescript
498
     * let cellRole = this.cell.role;
499
     * ```
500
     *
501
     * @memberof IgxGridCellComponent
502
     */
503
    @HostBinding('attr.role')
UNCOV
504
    public role = 'gridcell';
×
505

506
    /**
507
     * Gets whether the cell is editable.
508
     * ```typescript
509
     * let isCellReadonly = this.cell.readonly;
510
     * ```
511
     *
512
     * @memberof IgxGridCellComponent
513
     */
514
    @HostBinding('attr.aria-readonly')
515
    public get readonly(): boolean {
UNCOV
516
        return !this.editable;
×
517
    }
518

519
    /** @hidden @internal */
520
    @HostBinding('attr.aria-describedby')
521
    public get ariaDescribeBy() {
UNCOV
522
        let describeBy = (this.gridID + '_' + this.column.field).replace('.', '_');
×
UNCOV
523
        if (this.isInvalid) {
×
UNCOV
524
            describeBy += ' ' + this.ariaErrorMessage;
×
525
        }
UNCOV
526
        return describeBy;
×
527
    }
528

529
    /** @hidden @internal */
530
    public get ariaErrorMessage() {
UNCOV
531
        return this.grid.id + '_' + this.column.field + '_' + this.intRow.index + '_error';
×
532
    }
533

534
    /**
535
     * @hidden
536
     * @internal
537
     */
538
    @HostBinding('class.igx-grid__td--invalid')
539
    @HostBinding('attr.aria-invalid')
540
    public get isInvalid() {
UNCOV
541
        const isInvalid = this.formGroup?.get(this.column?.field)?.invalid && this.formGroup?.get(this.column?.field)?.touched;
×
UNCOV
542
        return !this.intRow.deleted && isInvalid;
×
543
    }
544

545
    /**
546
     * @hidden
547
     * @internal
548
     */
549
    @HostBinding('class.igx-grid__td--valid')
550
    public get isValidAfterEdit() {
UNCOV
551
        const formControl = this.formGroup?.get(this.column?.field);
×
UNCOV
552
        return this.editMode && formControl && !formControl.invalid && formControl.dirty;
×
553
    }
554

555
    /**
556
     * Gets the formControl responsible for value changes and validation for this cell.
557
     */
558
    protected get formControl(): FormControl {
UNCOV
559
        return this.grid.validation.getFormControl(this.intRow.key, this.column.field) as FormControl;
×
560
    }
561

562
    public get gridRowSpan(): number {
UNCOV
563
        return this.column.gridRowSpan;
×
564
    }
565

566
    public get gridColumnSpan(): number {
UNCOV
567
        return this.column.gridColumnSpan;
×
568
    }
569

570
    public get rowEnd(): number {
571
        return this.column.rowEnd;
×
572
    }
573

574
    public get colEnd(): number {
575
        return this.column.colEnd;
×
576
    }
577

578
    public get rowStart(): number {
579
        return this.column.rowStart;
×
580
    }
581

582
    public get colStart(): number {
583
        return this.column.colStart;
×
584
    }
585

586
    /**
587
     * Gets the width of the cell.
588
     * ```typescript
589
     * let cellWidth = this.cell.width;
590
     * ```
591
     *
592
     * @memberof IgxGridCellComponent
593
     */
594
    @Input()
UNCOV
595
    public width = '';
×
596

597
    /**
598
     * @hidden
599
     */
600
    @Input()
601
    @HostBinding('class.igx-grid__td--active')
UNCOV
602
    public active = false;
×
603

604
    @HostBinding('attr.aria-selected')
605
    public get ariaSelected() {
UNCOV
606
        return this.selected || this.column.selected || this.intRow.selected;
×
607
    }
608

609
    /**
610
     * Gets whether the cell is selected.
611
     * ```typescript
612
     * let isSelected = this.cell.selected;
613
     * ```
614
     *
615
     * @memberof IgxGridCellComponent
616
     */
617
    @HostBinding('class.igx-grid__td--selected')
618
    public get selected() {
UNCOV
619
        return this.selectionService.selected(this.selectionNode);
×
620
    }
621

622
    /**
623
     * Selects/deselects the cell.
624
     * ```typescript
625
     * this.cell.selected = true.
626
     * ```
627
     *
628
     * @memberof IgxGridCellComponent
629
     */
630
    public set selected(val: boolean) {
UNCOV
631
        const node = this.selectionNode;
×
UNCOV
632
        if (val) {
×
UNCOV
633
            this.selectionService.add(node);
×
634
        } else {
635
            this.selectionService.remove(node);
×
636
        }
UNCOV
637
        this.grid.notifyChanges();
×
638
    }
639

640
    /**
641
     * Gets whether the cell column is selected.
642
     * ```typescript
643
     * let isCellColumnSelected = this.cell.columnSelected;
644
     * ```
645
     *
646
     * @memberof IgxGridCellComponent
647
     */
648
    @HostBinding('class.igx-grid__td--column-selected')
649
    public get columnSelected() {
UNCOV
650
        return this.selectionService.isColumnSelected(this.column.field);
×
651
    }
652

653
    /**
654
     * Sets the current edit value while a cell is in edit mode.
655
     * Only for cell editing mode.
656
     * ```typescript
657
     * this.cell.editValue = value;
658
     * ```
659
     *
660
     * @memberof IgxGridCellComponent
661
     */
662
    public set editValue(value) {
UNCOV
663
        if (this.grid.crudService.cellInEditMode) {
×
UNCOV
664
            this.grid.crudService.cell.editValue = value;
×
665
        }
666
    }
667

668
    /**
669
     * Gets the current edit value while a cell is in edit mode.
670
     * Only for cell editing mode.
671
     * ```typescript
672
     * let editValue = this.cell.editValue;
673
     * ```
674
     *
675
     * @memberof IgxGridCellComponent
676
     */
677
    public get editValue() {
UNCOV
678
        if (this.grid.crudService.cellInEditMode) {
×
UNCOV
679
            return this.grid.crudService.cell.editValue;
×
680
        }
681
    }
682

683
    /**
684
     * Returns whether the cell is editable.
685
     */
686
    public get editable(): boolean {
UNCOV
687
        return this.column.editable && !this.intRow.disabled;
×
688
    }
689

690
    /**
691
     * @hidden
692
     */
693
    @Input()
694
    @HostBinding('class.igx-grid__td--row-pinned-first')
UNCOV
695
    public displayPinnedChip = false;
×
696

697
    @HostBinding('style.min-height.px')
698
    protected get minHeight() {
UNCOV
699
        if ((this.grid as any).isCustomSetRowHeight) {
×
UNCOV
700
            return this.grid.renderedRowHeight;
×
701
        }
702
    }
703

704
    @ViewChild('defaultCell', { read: TemplateRef, static: true })
705
    protected defaultCellTemplate: TemplateRef<any>;
706

707
    @ViewChild('defaultPinnedIndicator', { read: TemplateRef, static: true })
708
    protected defaultPinnedIndicator: TemplateRef<any>;
709

710
    @ViewChild('inlineEditor', { read: TemplateRef, static: true })
711
    protected inlineEditorTemplate: TemplateRef<any>;
712

713
    @ViewChild('addRowCell', { read: TemplateRef, static: true })
714
    protected addRowCellTemplate: TemplateRef<any>;
715

716
    @ViewChild(IgxTextHighlightDirective, { read: IgxTextHighlightDirective })
717
    protected set highlight(value: IgxTextHighlightDirective) {
UNCOV
718
        this._highlight = value;
×
719

UNCOV
720
        if (this._highlight && this.grid.lastSearchInfo.searchText) {
×
UNCOV
721
            this._highlight.highlight(this.grid.lastSearchInfo.searchText,
×
722
                this.grid.lastSearchInfo.caseSensitive,
723
                this.grid.lastSearchInfo.exactMatch);
UNCOV
724
            this._highlight.activateIfNecessary();
×
725
        }
726
    }
727

728
    protected get highlight() {
UNCOV
729
        return this._highlight;
×
730
    }
731

732
    protected get selectionNode(): ISelectionNode {
UNCOV
733
        return {
×
734
            row: this.rowIndex,
735
            column: this.column.columnLayoutChild ? this.column.parent.visibleIndex : this.visibleColumnIndex,
×
736
            layout: this.column.columnLayoutChild ? {
×
737
                rowStart: this.column.rowStart,
738
                colStart: this.column.colStart,
739
                rowEnd: this.column.rowEnd,
740
                colEnd: this.column.colEnd,
741
                columnVisibleIndex: this.visibleColumnIndex
742
            } : null
743
        };
744
    }
745

746
    /**
747
     * Sets/gets the highlight class of the cell.
748
     * Default value is `"igx-highlight"`.
749
     * ```typescript
750
     * let highlightClass = this.cell.highlightClass;
751
     * ```
752
     * ```typescript
753
     * this.cell.highlightClass = 'igx-cell-highlight';
754
     * ```
755
     *
756
     * @memberof IgxGridCellComponent
757
     */
UNCOV
758
    public highlightClass = 'igx-highlight';
×
759

760
    /**
761
     * Sets/gets the active highlight class class of the cell.
762
     * Default value is `"igx-highlight__active"`.
763
     * ```typescript
764
     * let activeHighlightClass = this.cell.activeHighlightClass;
765
     * ```
766
     * ```typescript
767
     * this.cell.activeHighlightClass = 'igx-cell-highlight_active';
768
     * ```
769
     *
770
     * @memberof IgxGridCellComponent
771
     */
UNCOV
772
    public activeHighlightClass = 'igx-highlight__active';
×
773

774
    /** @hidden @internal */
775
    public get step(): number {
UNCOV
776
        const digitsInfo = this.column.pipeArgs.digitsInfo;
×
UNCOV
777
        if (!digitsInfo) {
×
778
            return 1;
×
779
        }
UNCOV
780
        const step = +digitsInfo.substr(digitsInfo.indexOf('.') + 1, 1);
×
UNCOV
781
        return 1 / (Math.pow(10, step));
×
782
    }
783

784
    /** @hidden @internal */
785
    public get currencyCode(): string {
UNCOV
786
        return this.column.pipeArgs.currencyCode ?
×
787
            this.column.pipeArgs.currencyCode : getLocaleCurrencyCode(this.grid.locale);
788
    }
789

790
    /** @hidden @internal */
791
    public get currencyCodeSymbol(): string {
UNCOV
792
        return getCurrencySymbol(this.currencyCode, 'wide', this.grid.locale);
×
793
    }
794

795
    protected _lastSearchInfo: ISearchInfo;
796
    private _highlight: IgxTextHighlightDirective;
UNCOV
797
    private _cellSelection: GridSelectionMode = GridSelectionMode.multiple;
×
UNCOV
798
    private _vIndex = -1;
×
799

800
    constructor(
UNCOV
801
        protected selectionService: IgxGridSelectionService,
×
UNCOV
802
        @Inject(IGX_GRID_BASE) public grid: GridType,
×
UNCOV
803
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
×
UNCOV
804
        public cdr: ChangeDetectorRef,
×
UNCOV
805
        private element: ElementRef<HTMLElement>,
×
UNCOV
806
        protected zone: NgZone,
×
UNCOV
807
        private touchManager: HammerGesturesManager,
×
UNCOV
808
        protected platformUtil: PlatformUtil
×
809
    ) { }
810

811
    /**
812
     * @hidden
813
     * @internal
814
     */
815
    @HostListener('dblclick', ['$event'])
UNCOV
816
    public onDoubleClick = (event: MouseEvent) => {
×
UNCOV
817
        if (event.type === 'doubletap') {
×
818
            // prevent double-tap to zoom on iOS
UNCOV
819
            event.preventDefault();
×
820
        }
UNCOV
821
        if (this.editable && !this.editMode && !this.intRow.deleted && !this.grid.crudService.rowEditingBlocked) {
×
UNCOV
822
            this.grid.crudService.enterEditMode(this, event as Event);
×
823
        }
824

UNCOV
825
        this.grid.doubleClick.emit({
×
826
            cell: this.getCellType(),
827
            event
828
        });
829
    };
830

831
    /**
832
     * @hidden
833
     * @internal
834
     */
835
    @HostListener('click', ['$event'])
836
    public onClick(event: MouseEvent) {
UNCOV
837
        this.grid.cellClick.emit({
×
838
            cell: this.getCellType(),
839
            event
840
        });
841
    }
842

843
    /**
844
     * @hidden
845
     * @internal
846
     */
847
    public ngOnInit() {
UNCOV
848
        this.zone.runOutsideAngular(() => {
×
UNCOV
849
            this.nativeElement.addEventListener('pointerdown', this.pointerdown);
×
UNCOV
850
            this.addPointerListeners(this.cellSelectionMode);
×
851
        });
UNCOV
852
        if (this.platformUtil.isIOS) {
×
UNCOV
853
            this.touchManager.addEventListener(this.nativeElement, 'doubletap', this.onDoubleClick, {
×
854
                cssProps: {} /* don't disable user-select, etc */
855
            });
856
        }
857

858
    }
859

860
    public ngAfterViewInit() {
UNCOV
861
        this.errorTooltip.changes.pipe(takeUntil(this._destroy$)).subscribe(() => {
×
UNCOV
862
            if (this.errorTooltip.length > 0 && this.active) {
×
863
                // error ocurred
UNCOV
864
                this.cdr.detectChanges();
×
UNCOV
865
                this.openErrorTooltip();
×
866
            }
867
        });
868
    }
869

870
    /**
871
     * @hidden
872
     * @internal
873
     */
UNCOV
874
    public errorShowing = false;
×
875

876
    private openErrorTooltip() {
UNCOV
877
        const tooltip = this.errorTooltip.first;
×
UNCOV
878
        tooltip.open(
×
879
            {
880
                target: this.errorIcon.el.nativeElement,
881
                closeOnOutsideClick: true,
882
                excludeFromOutsideClick: [this.nativeElement],
883
                closeOnEscape: false,
884
                outlet: this.grid.outlet,
885
                modal: false,
886
                positionStrategy: new AutoPositionStrategy({
887
                    horizontalStartPoint: HorizontalAlignment.Center,
888
                    horizontalDirection: HorizontalAlignment.Center,
889
                    openAnimation: useAnimation(scaleInCenter, { params: { duration: '150ms' } }),
890
                    closeAnimation: useAnimation(fadeOut, { params: { duration: '75ms' } })
891
                })
892
            }
893
        );
894
    }
895

896
    /**
897
     * @hidden
898
     * @internal
899
     */
900
    public ngOnDestroy() {
UNCOV
901
        this.zone.runOutsideAngular(() => {
×
UNCOV
902
            this.nativeElement.removeEventListener('pointerdown', this.pointerdown);
×
UNCOV
903
            this.removePointerListeners(this.cellSelectionMode);
×
904
        });
UNCOV
905
        this.touchManager.destroy();
×
UNCOV
906
        this._destroy$.next();
×
UNCOV
907
        this._destroy$.complete();
×
908
    }
909

910
    /**
911
     * @hidden
912
     * @internal
913
     */
914
    public ngOnChanges(changes: SimpleChanges): void {
UNCOV
915
        if (changes.editMode && changes.editMode.currentValue && this.formControl) {
×
916
            // ensure when values change, form control is forced to be marked as touche.
UNCOV
917
            this.formControl.valueChanges.pipe(takeWhile(() => this.editMode)).subscribe(() => this.formControl.markAsTouched());
×
918
            // while in edit mode subscribe to value changes on the current form control and set to editValue
UNCOV
919
            this.formControl.statusChanges.pipe(takeWhile(() => this.editMode)).subscribe(status => {
×
UNCOV
920
                if (status === 'INVALID' && this.errorTooltip.length > 0) {
×
921
                    this.cdr.detectChanges();
×
922
                    const tooltip = this.errorTooltip.first;
×
923
                    this.resizeAndRepositionOverlayById(tooltip.overlayId, this.errorTooltip.first.element.offsetWidth);
×
924
                }
925
            });
926
        }
UNCOV
927
        if (changes.value && !changes.value.firstChange) {
×
UNCOV
928
            if (this.highlight) {
×
UNCOV
929
                this.highlight.lastSearchInfo.searchText = this.grid.lastSearchInfo.searchText;
×
UNCOV
930
                this.highlight.lastSearchInfo.caseSensitive = this.grid.lastSearchInfo.caseSensitive;
×
UNCOV
931
                this.highlight.lastSearchInfo.exactMatch = this.grid.lastSearchInfo.exactMatch;
×
932
            }
UNCOV
933
            const isInEdit = this.grid.rowEditable ? this.row.inEditMode : this.editMode;
×
UNCOV
934
            if (this.formControl && this.formControl.value !== changes.value.currentValue && !isInEdit) {
×
UNCOV
935
                this.formControl.setValue(changes.value.currentValue);
×
936
            }
937
        }
938
    }
939

940

941

942
    /**
943
     * @hidden @internal
944
     */
945
    private resizeAndRepositionOverlayById(overlayId: string, newSize: number) {
946
        const overlay = this.overlayService.getOverlayById(overlayId);
×
947
        if (!overlay) return;
×
948
        overlay.initialSize.width = newSize;
×
949
        overlay.elementRef.nativeElement.parentElement.style.width = newSize + 'px';
×
950
        this.overlayService.reposition(overlayId);
×
951
    }
952

953
    /**
954
     * Starts/ends edit mode for the cell.
955
     *
956
     * ```typescript
957
     * cell.setEditMode(true);
958
     * ```
959
     */
960
    public setEditMode(value: boolean): void {
UNCOV
961
        if (this.intRow.deleted) {
×
962
            return;
×
963
        }
UNCOV
964
        if (this.editable && value) {
×
UNCOV
965
            if (this.grid.crudService.cellInEditMode) {
×
UNCOV
966
                this.grid.gridAPI.update_cell(this.grid.crudService.cell);
×
UNCOV
967
                this.grid.crudService.endCellEdit();
×
968
            }
UNCOV
969
            this.grid.crudService.enterEditMode(this);
×
970
        } else {
UNCOV
971
            this.grid.crudService.endCellEdit();
×
972
        }
UNCOV
973
        this.grid.notifyChanges();
×
974
    }
975

976
    /**
977
     * Sets new value to the cell.
978
     * ```typescript
979
     * this.cell.update('New Value');
980
     * ```
981
     *
982
     * @memberof IgxGridCellComponent
983
     */
984
    // TODO: Refactor
985
    public update(val: any) {
UNCOV
986
        if (this.intRow.deleted) {
×
987
            return;
×
988
        }
989

UNCOV
990
        let cell = this.grid.crudService.cell;
×
UNCOV
991
        if (!cell) {
×
UNCOV
992
            cell = this.grid.crudService.createCell(this);
×
993
        }
UNCOV
994
        cell.editValue = val;
×
UNCOV
995
        this.grid.gridAPI.update_cell(cell);
×
UNCOV
996
        this.grid.crudService.endCellEdit();
×
UNCOV
997
        this.cdr.markForCheck();
×
998
    }
999

1000
    /**
1001
     *
1002
     * @hidden
1003
     * @internal
1004
     */
UNCOV
1005
    public pointerdown = (event: PointerEvent) => {
×
UNCOV
1006
        if (this.cellSelectionMode !== GridSelectionMode.multiple) {
×
UNCOV
1007
            this.activate(event);
×
UNCOV
1008
            return;
×
1009
        }
UNCOV
1010
        if (!this.platformUtil.isLeftClick(event)) {
×
UNCOV
1011
            event.preventDefault();
×
UNCOV
1012
            this.grid.navigation.setActiveNode({ rowIndex: this.rowIndex, colIndex: this.visibleColumnIndex });
×
UNCOV
1013
            this.selectionService.addKeyboardRange();
×
UNCOV
1014
            this.selectionService.initKeyboardState();
×
UNCOV
1015
            this.selectionService.primaryButton = false;
×
1016
            // Ensure RMB Click on edited cell does not end cell editing
UNCOV
1017
            if (!this.selected) {
×
UNCOV
1018
                this.grid.crudService.updateCell(true, event);
×
1019
            }
UNCOV
1020
            return;
×
1021
        } else {
UNCOV
1022
            this.selectionService.primaryButton = true;
×
1023
        }
UNCOV
1024
        this.selectionService.pointerDown(this.selectionNode, event.shiftKey, event.ctrlKey);
×
UNCOV
1025
        this.activate(event);
×
1026
    };
1027

1028
    /**
1029
     *
1030
     * @hidden
1031
     * @internal
1032
     */
UNCOV
1033
    public pointerenter = (event: PointerEvent) => {
×
UNCOV
1034
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
×
UNCOV
1035
        if (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID || this.grid.navigation.activeNode.gridID !== this.gridID)) {
×
UNCOV
1036
            return;
×
1037
        }
UNCOV
1038
        const dragMode = this.selectionService.pointerEnter(this.selectionNode, event);
×
UNCOV
1039
        if (dragMode) {
×
UNCOV
1040
            this.grid.cdr.detectChanges();
×
1041
        }
1042
    };
1043

1044
    /**
1045
     * @hidden
1046
     * @internal
1047
     */
UNCOV
1048
    public focusout = () => {
×
UNCOV
1049
        this.closeErrorTooltip();
×
1050
    }
1051

1052
    private closeErrorTooltip() {
UNCOV
1053
        const tooltip = this.errorTooltip.first;
×
UNCOV
1054
        if (tooltip) {
×
1055
            tooltip.close();
×
1056
        }
1057
    }
1058

1059
    /**
1060
     * @hidden
1061
     * @internal
1062
     */
UNCOV
1063
    public pointerup = (event: PointerEvent) => {
×
UNCOV
1064
        const isHierarchicalGrid = this.grid.type === 'hierarchical';
×
UNCOV
1065
        if (!this.platformUtil.isLeftClick(event) || (isHierarchicalGrid && (!this.grid.navigation?.activeNode?.gridID ||
×
1066
            this.grid.navigation.activeNode.gridID !== this.gridID))) {
UNCOV
1067
            return;
×
1068
        }
UNCOV
1069
        if (this.selectionService.pointerUp(this.selectionNode, this.grid.rangeSelected)) {
×
UNCOV
1070
            this.grid.cdr.detectChanges();
×
1071
        }
1072
    };
1073

1074
    /**
1075
     * @hidden
1076
     * @internal
1077
     */
1078
    public activate(event: FocusEvent | KeyboardEvent) {
UNCOV
1079
        const node = this.selectionNode;
×
UNCOV
1080
        let shouldEmitSelection = !this.selectionService.isActiveNode(node);
×
1081

UNCOV
1082
        if (this.selectionService.primaryButton) {
×
UNCOV
1083
            const currentActive = this.selectionService.activeElement;
×
UNCOV
1084
            if (this.cellSelectionMode === GridSelectionMode.single && (event as any)?.ctrlKey && this.selected) {
×
UNCOV
1085
                this.selectionService.activeElement = null;
×
UNCOV
1086
                shouldEmitSelection = true;
×
1087
            } else {
UNCOV
1088
                this.selectionService.activeElement = node;
×
1089
            }
UNCOV
1090
            const cancel = this._updateCRUDStatus(event);
×
UNCOV
1091
            if (cancel) {
×
UNCOV
1092
                this.selectionService.activeElement = currentActive;
×
UNCOV
1093
                return;
×
1094
            }
1095

UNCOV
1096
            const activeElement = this.selectionService.activeElement;
×
UNCOV
1097
            const row = activeElement ? this.grid.gridAPI.get_row_by_index(activeElement.row) : null;
×
UNCOV
1098
            if (this.grid.crudService.rowEditingBlocked && row && this.intRow.key !== row.key) {
×
1099
                return;
×
1100
            }
1101

1102
        } else {
1103
            this.selectionService.activeElement = null;
×
1104
            if (this.grid.crudService.cellInEditMode && !this.editMode) {
×
1105
                this.grid.crudService.updateCell(true, event);
×
1106
            }
1107
        }
1108

UNCOV
1109
        this.grid.navigation.setActiveNode({ row: this.rowIndex, column: this.visibleColumnIndex });
×
1110

UNCOV
1111
        const isTargetErrorIcon = event && event.target && event.target === this.errorIcon?.el.nativeElement
×
UNCOV
1112
        if (this.isInvalid && !isTargetErrorIcon) {
×
UNCOV
1113
            this.cdr.detectChanges();
×
UNCOV
1114
            this.openErrorTooltip();
×
UNCOV
1115
            this.grid.activeNodeChange.pipe(first()).subscribe(() => {
×
1116
                this.closeErrorTooltip();
×
1117
            });
1118
        }
UNCOV
1119
        this.selectionService.primaryButton = true;
×
UNCOV
1120
        if (this.cellSelectionMode === GridSelectionMode.multiple && this.selectionService.activeElement) {
×
UNCOV
1121
            if (this.selectionService.isInMap(this.selectionService.activeElement) && (event as any)?.ctrlKey && !(event as any)?.shiftKey) {
×
UNCOV
1122
                this.selectionService.remove(this.selectionService.activeElement);
×
UNCOV
1123
                shouldEmitSelection = true;
×
1124
            } else {
UNCOV
1125
                this.selectionService.add(this.selectionService.activeElement, false); // pointer events handle range generation
×
UNCOV
1126
                this.selectionService.keyboardStateOnFocus(node, this.grid.rangeSelected, this.nativeElement);
×
1127
            }
1128
        }
UNCOV
1129
        if (this.grid.isCellSelectable && shouldEmitSelection) {
×
UNCOV
1130
            this.zone.run(() => this.grid.selected.emit({ cell: this.getCellType(), event }));
×
1131
        }
1132
    }
1133

1134
    /**
1135
     * If the provided string matches the text in the cell, the text gets highlighted.
1136
     * ```typescript
1137
     * this.cell.highlightText('Cell Value', true);
1138
     * ```
1139
     *
1140
     * @memberof IgxGridCellComponent
1141
     */
1142
    public highlightText(text: string, caseSensitive?: boolean, exactMatch?: boolean): number {
UNCOV
1143
        return this.highlight && this.column.searchable ? this.highlight.highlight(text, caseSensitive, exactMatch) : 0;
×
1144
    }
1145

1146
    /**
1147
     * Clears the highlight of the text in the cell.
1148
     * ```typescript
1149
     * this.cell.clearHighLight();
1150
     * ```
1151
     *
1152
     * @memberof IgxGridCellComponent
1153
     */
1154
    public clearHighlight() {
UNCOV
1155
        if (this.highlight && this.column.searchable) {
×
UNCOV
1156
            this.highlight.clearHighlight();
×
1157
        }
1158
    }
1159

1160
    /**
1161
     * @hidden
1162
     * @internal
1163
     */
1164
    public calculateSizeToFit(range: any): number {
UNCOV
1165
        return this.platformUtil.getNodeSizeViaRange(range, this.nativeElement);
×
1166
    }
1167

1168
    /**
1169
     * @hidden
1170
     * @internal
1171
     */
1172
    public get searchMetadata() {
UNCOV
1173
        const meta = new Map<string, any>();
×
UNCOV
1174
        meta.set('pinned', this.grid.isRecordPinnedByViewIndex(this.intRow.index));
×
UNCOV
1175
        return meta;
×
1176
    }
1177

1178
    /**
1179
     * @hidden
1180
     * @internal
1181
     */
1182
    private _updateCRUDStatus(event?: Event) {
UNCOV
1183
        if (this.editMode) {
×
UNCOV
1184
            return;
×
1185
        }
1186

1187
        let editableArgs;
UNCOV
1188
        const crud = this.grid.crudService;
×
UNCOV
1189
        const editableCell = this.grid.crudService.cell;
×
UNCOV
1190
        const editMode = !!(crud.row || crud.cell);
×
1191

UNCOV
1192
        if (this.editable && editMode && !this.intRow.deleted) {
×
UNCOV
1193
            if (editableCell) {
×
UNCOV
1194
                editableArgs = this.grid.crudService.updateCell(false, event);
×
1195

1196
                /* This check is related with the following issue #6517:
1197
                 * when edit cell that belongs to a column which is sorted and press tab,
1198
                 * the next cell in edit mode is with wrong value /its context is not updated/;
1199
                 * So we reapply sorting before the next cell enters edit mode.
1200
                 * Also we need to keep the notifyChanges below, because of the current
1201
                 * change detection cycle when we have editing with enabled transactions
1202
                 */
UNCOV
1203
                if (this.grid.sortingExpressions.length && this.grid.sortingExpressions.indexOf(editableCell.column.field)) {
×
1204
                    this.grid.cdr.detectChanges();
×
1205
                }
1206

UNCOV
1207
                if (editableArgs && editableArgs.cancel) {
×
UNCOV
1208
                    return true;
×
1209
                }
1210

UNCOV
1211
                crud.exitCellEdit(event);
×
1212
            }
UNCOV
1213
            this.grid.tbody.nativeElement.focus({ preventScroll: true });
×
UNCOV
1214
            this.grid.notifyChanges();
×
UNCOV
1215
            crud.enterEditMode(this, event);
×
UNCOV
1216
            return false;
×
1217
        }
1218

UNCOV
1219
        if (editableCell && crud.sameRow(this.cellID.rowID)) {
×
UNCOV
1220
            this.grid.crudService.updateCell(true, event);
×
UNCOV
1221
        } else if (editMode && !crud.sameRow(this.cellID.rowID)) {
×
UNCOV
1222
            this.grid.crudService.endEdit(true, event);
×
1223
        }
1224
    }
1225

1226
    private addPointerListeners(selection) {
UNCOV
1227
        if (selection !== GridSelectionMode.multiple) {
×
UNCOV
1228
            return;
×
1229
        }
UNCOV
1230
        this.nativeElement.addEventListener('pointerenter', this.pointerenter);
×
UNCOV
1231
        this.nativeElement.addEventListener('pointerup', this.pointerup);
×
UNCOV
1232
        this.nativeElement.addEventListener('focusout', this.focusout);
×
1233
    }
1234

1235
    private removePointerListeners(selection) {
UNCOV
1236
        if (selection !== GridSelectionMode.multiple) {
×
UNCOV
1237
            return;
×
1238
        }
UNCOV
1239
        this.nativeElement.removeEventListener('pointerenter', this.pointerenter);
×
UNCOV
1240
        this.nativeElement.removeEventListener('pointerup', this.pointerup);
×
UNCOV
1241
        this.nativeElement.removeEventListener('focusout', this.focusout);
×
1242
    }
1243

1244
    private getCellType(useRow?: boolean): CellType {
UNCOV
1245
        const rowID = useRow ? this.grid.createRow(this.intRow.index, this.intRow.data) : this.intRow.index;
×
UNCOV
1246
        return new IgxGridCell(this.grid, rowID, this.column);
×
1247
    }
1248
}
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