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

IgniteUI / igniteui-angular / 29568001031

17 Jul 2026 08:53AM UTC coverage: 90.137% (+0.002%) from 90.135%
29568001031

Pull #17328

github

web-flow
Merge 001d30684 into 8ebabbb38
Pull Request #17328: fix(grid): fix zone.onStable patterns broken in zoneless change detection

14906 of 17373 branches covered (85.8%)

Branch coverage included in aggregate %.

29 of 31 new or added lines in 7 files covered. (93.55%)

40 existing lines in 10 files now uncovered.

29984 of 32429 relevant lines covered (92.46%)

37356.35 hits per line

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

90.6
/projects/igniteui-angular/input-group/src/input-group/input-group.component.ts
1
import { NgTemplateOutlet } from '@angular/common';
2
import {
3
  ChangeDetectorRef,
4
  Component,
5
  ContentChild,
6
  ContentChildren,
7
  DestroyRef,
8
  ElementRef,
9
  HostBinding,
10
  HostListener, Input,
11
  QueryList, booleanAttribute,
12
  inject,
13
  AfterContentChecked,
14
  ChangeDetectionStrategy
15
} from '@angular/core';
16
import { IInputResourceStrings, InputResourceStringsEN } from 'igniteui-angular/core';
17
import { getComponentTheme } from 'igniteui-angular/core';
18
import { IgxButtonDirective } from 'igniteui-angular/directives';
19
import { IgxHintDirective } from './directives-hint/hint.directive';
20
import {
21
    IgxInputDirective,
22
    IgxInputState
23
} from './directives-input/input.directive';
24
import { IgxPrefixDirective } from './directives-prefix/prefix.directive';
25
import { IgxSuffixDirective } from './directives-suffix/suffix.directive';
26

27
import { IgxInputGroupBase } from './input-group.common';
28
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from './inputGroupType';
29
import { IgxIconComponent } from 'igniteui-angular/icon';
30
import { getCurrentResourceStrings, onResourceChangeHandle } from 'igniteui-angular/core';
31
import { IgxTheme, THEME_TOKEN, ThemeToken } from 'igniteui-angular/core';
32

33
@Component({
34
    selector: 'igx-input-group',
35
    templateUrl: 'input-group.component.html',
36
    providers: [{ provide: IgxInputGroupBase, useExisting: IgxInputGroupComponent }],
37
    changeDetection: ChangeDetectionStrategy.Eager,
38
    imports: [NgTemplateOutlet, IgxPrefixDirective, IgxButtonDirective, IgxSuffixDirective, IgxIconComponent]
39
})
40
export class IgxInputGroupComponent implements IgxInputGroupBase, AfterContentChecked {
3✔
41
    public element = inject<ElementRef<HTMLElement>>(ElementRef);
3,890✔
42
    private _inputGroupType = inject<IgxInputGroupType>(IGX_INPUT_GROUP_TYPE, { optional: true });
3,890✔
43
    private cdr = inject(ChangeDetectorRef);
3,890✔
44
    private themeToken = inject<ThemeToken>(THEME_TOKEN);
3,890✔
45

46
    /**
47
     * Sets the resource strings.
48
     * By default it uses EN resources.
49
     */
50
    @Input()
51
    public set resourceStrings(value: IInputResourceStrings) {
UNCOV
52
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
53
    }
54

55
    /**
56
     * Returns the resource strings.
57
     */
58
    public get resourceStrings(): IInputResourceStrings {
59
        return this._resourceStrings || this._defaultResourceStrings;
105✔
60
    }
61

62
    /**
63
     * Property that enables/disables the auto-generated class of the input group.
64
     * By default applied the class is applied.
65
     * ```typescript
66
     *  @ViewChild("MyInputGroup")
67
     *  public inputGroup: IgxInputGroupComponent;
68
     *  ngAfterViewInit(){
69
     *  this.inputGroup.defaultClass = false;
70
     * ```
71
     * }
72
     */
73
    @HostBinding('class.igx-input-group')
74
    public defaultClass = true;
3,890✔
75

76
    /** @hidden */
77
    @HostBinding('class.igx-input-group--placeholder')
78
    public hasPlaceholder = false;
3,890✔
79

80
    /** @hidden */
81
    @HostBinding('class.igx-input-group--required')
82
    public isRequired = false;
3,890✔
83

84
    /** @hidden */
85
    @HostBinding('class.igx-input-group--focused')
86
    public isFocused = false;
3,890✔
87

88
    /**
89
     * @hidden @internal
90
     * When truthy, disables the input group.
91
     * Controlled by the underlying input.
92
     * ```html
93
     * <igx-input-group [disabled]="true"></igx-input-group>
94
     * ```
95
     */
96
    @HostBinding('class.igx-input-group--disabled')
97
    public disabled = false;
3,890✔
98

99
    /**
100
     * Prevents automatically focusing the input when clicking on other elements in the input group (e.g. prefix or suffix).
101
     *
102
     * @remarks Automatic focus causes software keyboard to show on mobile devices.
103
     *
104
     * @example
105
     * ```html
106
     * <igx-input-group [suppressInputAutofocus]="true"></igx-input-group>
107
     * ```
108
     */
109
    @Input({ transform: booleanAttribute })
110
    public suppressInputAutofocus = false;
3,890✔
111

112
    /** @hidden */
113
    @HostBinding('class.igx-input-group--warning')
114
    public hasWarning = false;
3,890✔
115

116
    /** @hidden */
117
    @ContentChildren(IgxHintDirective, { read: IgxHintDirective })
118
    protected hints: QueryList<IgxHintDirective>;
119

120
    @ContentChildren(IgxPrefixDirective, { read: IgxPrefixDirective, descendants: true })
121
    protected _prefixes: QueryList<IgxPrefixDirective>;
122

123
    @ContentChildren(IgxSuffixDirective, { read: IgxSuffixDirective, descendants: true })
124
    protected _suffixes: QueryList<IgxSuffixDirective>;
125

126
    /** @hidden */
127
    @ContentChild(IgxInputDirective, { read: IgxInputDirective, static: true })
128
    protected input: IgxInputDirective;
129

130
    private _destroyRef = inject(DestroyRef);
3,890✔
131
    private _type: IgxInputGroupType = null;
3,890✔
132
    private _filled = false;
3,890✔
133
    private _theme: IgxTheme;
134
    private _resourceStrings: IInputResourceStrings = null;
3,890✔
135
    private _defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN);
3,890✔
136
    private _readOnly: undefined | boolean;
137

138
    /** @hidden @internal */
139
    @HostBinding('class.igx-input-group--readonly')
140
    public get readOnly(): boolean {
141
        return this._readOnly ?? (this.input?.nativeElement.readOnly || false);
67,152✔
142
    }
143

144
    /** @hidden @internal */
145
    public set readOnly(value: boolean) {
146
        this._readOnly = value;
1,728✔
147
    }
148

149
    /** @hidden */
150
    @HostBinding('class.igx-input-group--valid')
151
    public get validClass(): boolean {
152
        return this.input.valid === IgxInputState.VALID;
67,152✔
153
    }
154

155
    /** @hidden */
156
    @HostBinding('class.igx-input-group--invalid')
157
    public get invalidClass(): boolean {
158
        return this.input.valid === IgxInputState.INVALID;
67,152✔
159
    }
160

161
    /** @hidden */
162
    @HostBinding('class.igx-input-group--filled')
163
    public get isFilled() {
164
        return this._filled || (this.input && this.input.value);
67,257✔
165
    }
166

167
    /** @hidden */
168
    @HostBinding('class.igx-input-group--textarea-group')
169
    public get textAreaClass(): boolean {
170
        return this.input.isTextArea;
67,152✔
171
    }
172

173
    /**
174
     * Sets how the input will be styled.
175
     * Allowed values of type input group type.
176
     * ```html
177
     * <igx-input-group [type]="'search'">
178
     * ```
179
     */
180
    @Input()
181
    public set type(value: IgxInputGroupType) {
182
        this._type = value;
3,050✔
183
    }
184

185
    /**
186
     * Returns the type of the input group. How the input is styled.
187
     * The default is `box`.
188
     * ```typescript
189
     * @ViewChild("MyInputGroup")
190
     * public inputGroup: IgxInputGroupComponent;
191
     * ngAfterViewInit(){
192
     *    let inputType = this.inputGroup.type;
193
     * }
194
     * ```
195
     */
196
    public get type() {
197
        return this._type || this._inputGroupType || 'box';
395,015✔
198
    }
199

200
    /**
201
     * Sets the theme of the input.
202
     * Allowed values of type input group theme.
203
     * ```typescript
204
     * @ViewChild("MyInputGroup")
205
     * public inputGroup: IgxInputGroupComponent;
206
     * ngAfterViewInit() {
207
     *  let inputTheme = 'fluent';
208
     * }
209
     */
210
    @Input()
211
    public set theme(value: IgxTheme) {
UNCOV
212
        this._theme = value;
×
213
    }
214

215
    /**
216
     * Returns the theme of the input.
217
     * The returned value is of type input group type.
218
     * ```typescript
219
     * @ViewChild("MyInputGroup")
220
     * public inputGroup: IgxInputGroupComponent;
221
     * ngAfterViewInit() {
222
     *  let inputTheme = this.inputGroup.theme;
223
     * }
224
     */
225
    public get theme(): IgxTheme {
226
        return this._theme;
67,151✔
227
    }
228

229
    constructor() {
230
        this._theme = this.themeToken.theme;
3,890✔
231
        const themeChange = this.themeToken.onChange((theme) => {
3,890✔
232
            if (this._theme !== theme) {
3,890!
UNCOV
233
                this._theme = theme;
×
UNCOV
234
                this.cdr.detectChanges();
×
235
            }
236
        });
237
        this._destroyRef.onDestroy(() => themeChange.unsubscribe());
3,890✔
238
        onResourceChangeHandle(this._destroyRef, () => {
3,890✔
239
            this._defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN, false);
546✔
240
        }, this);
241
    }
242

243
    /** @hidden */
244
    @HostListener('click', ['$event'])
245
    public onClick(event: MouseEvent) {
246
        if (
290✔
247
            !this.isFocused &&
660✔
248
            event.target !== this.input.nativeElement &&
249
            !this.suppressInputAutofocus
250
        ) {
251
            this.input.focus();
172✔
252
        }
253
    }
254

255
    /** @hidden */
256
    @HostListener('pointerdown', ['$event'])
257
    public onPointerDown(event: PointerEvent) {
258
        if (this.isFocused && event.target !== this.input.nativeElement) {
19✔
259
            event.preventDefault();
3✔
260
        }
261
    }
262

263
    /** @hidden @internal */
264
    public hintClickHandler(event: MouseEvent) {
265
        event.stopPropagation();
2✔
266
    }
267

268
    /**
269
     * Returns whether the input group has hints.
270
     * ```typescript
271
     * @ViewChild("MyInputGroup")
272
     * public inputGroup: IgxInputGroupComponent;
273
     * ngAfterViewInit(){
274
     *    let inputHints = this.inputGroup.hasHints;
275
     * }
276
     * ```
277
     */
278
    public get hasHints() {
UNCOV
279
        return this.hints.length > 0;
×
280
    }
281

282
    /** @hidden @internal */
283
    @HostBinding('class.igx-input-group--prefixed')
284
    public get hasPrefixes() {
285
        return this._prefixes.length > 0;
67,152✔
286
    }
287

288
    /** @hidden @internal */
289
    public set prefixes(items: QueryList<IgxPrefixDirective>) {
290
        this._prefixes = items;
879✔
291
    }
292

293
    /** @hidden @internal */
294
    @HostBinding('class.igx-input-group--suffixed')
295
    public get hasSuffixes() {
296
        return this._suffixes.length > 0 || this.isFileType && this.isFilled;
67,152✔
297
    }
298

299
    /** @hidden @internal */
300
    public set suffixes(items: QueryList<IgxSuffixDirective>) {
301
        this._suffixes = items;
18,460✔
302
    }
303

304
    /**
305
     * Returns whether the input group has border.
306
     * ```typescript
307
     * @ViewChild("MyInputGroup")
308
     * public inputGroup: IgxInputGroupComponent;
309
     * ngAfterViewInit(){
310
     *    let inputBorder = this.inputGroup.hasBorder;
311
     * }
312
     * ```
313
     */
314
    public get hasBorder() {
315
        return (
67,151✔
316
            (this.type === 'line' || this.type === 'box') &&
190,586✔
317
            this._theme === 'material'
318
        );
319
    }
320

321
    /**
322
     * Returns whether the input group type is line.
323
     * ```typescript
324
     * @ViewChild("MyInputGroup1")
325
     * public inputGroup: IgxInputGroupComponent;
326
     * ngAfterViewInit(){
327
     *    let isTypeLine = this.inputGroup.isTypeLine;
328
     * }
329
     * ```
330
     */
331
    public get isTypeLine(): boolean {
332
        return this.type === 'line' && this._theme === 'material';
10✔
333
    }
334

335
    /**
336
     * Returns whether the input group type is box.
337
     * ```typescript
338
     * @ViewChild("MyInputGroup1")
339
     * public inputGroup: IgxInputGroupComponent;
340
     * ngAfterViewInit(){
341
     *    let isTypeBox = this.inputGroup.isTypeBox;
342
     * }
343
     * ```
344
     */
345
    @HostBinding('class.igx-input-group--box')
346
    public get isTypeBox() {
347
        return this.type === 'box' && this._theme === 'material';
134,313✔
348
    }
349

350
    /** @hidden @internal */
351
    public clearValueHandler() {
352
        this.input.clear();
2✔
353
    }
354

355
    /** @hidden @internal */
356
    @HostBinding('class.igx-input-group--file')
357
    public get isFileType() {
358
        return this.input.type === 'file';
563,570✔
359
    }
360

361
    /** @hidden @internal */
362
    @HostBinding('class.igx-file-input')
363
    public get isFileInput() {
364
        return this.input.type === 'file';
67,152✔
365
    }
366

367
    /** @hidden @internal */
368
    @HostBinding('class.igx-file-input--filled')
369
    public get isFileInputFilled() {
370
        return this.isFileType && this.isFilled;
67,152✔
371
    }
372

373
    /** @hidden @internal */
374
    @HostBinding('class.igx-file-input--focused')
375
    public get isFileInputFocused() {
376
        return this.isFileType && this.isFocused;
67,152✔
377
    }
378

379
    /** @hidden @internal */
380
    @HostBinding('class.igx-file-input--disabled')
381
    public get isFileInputDisabled() {
382
        return this.isFileType && this.disabled;
67,152✔
383
    }
384

385
    /** @hidden @internal */
386
    public get fileNames() {
387
        return this.input.fileNames || this.resourceStrings.igx_input_file_placeholder;
70✔
388
    }
389

390
    /**
391
     * Returns whether the input group type is border.
392
     * ```typescript
393
     * @ViewChild("MyInputGroup1")
394
     * public inputGroup: IgxInputGroupComponent;
395
     * ngAfterViewInit(){
396
     *    let isTypeBorder = this.inputGroup.isTypeBorder;
397
     * }
398
     * ```
399
     */
400
    @HostBinding('class.igx-input-group--border')
401
    public get isTypeBorder() {
402
        return this.type === 'border' && this._theme === 'material';
67,162✔
403
    }
404

405
    /**
406
     * Returns true if the input group theme is Fluent.
407
     * ```typescript
408
     * @ViewChild("MyInputGroup1")
409
     * public inputGroup: IgxInputGroupComponent;
410
     * ngAfterViewInit(){
411
     *    let isTypeFluent = this.inputGroup.isTypeFluent;
412
     * }
413
     * ```
414
     */
415
    @HostBinding('class.igx-input-group--fluent')
416
    public get isTypeFluent() {
417
        return this._theme === 'fluent';
67,152✔
418
    }
419

420
    /**
421
     * Returns true if the input group theme is Bootstrap.
422
     * ```typescript
423
     * @ViewChild("MyInputGroup1")
424
     * public inputGroup: IgxInputGroupComponent;
425
     * ngAfterViewInit(){
426
     *    let isTypeBootstrap = this.inputGroup.isTypeBootstrap;
427
     * }
428
     * ```
429
     */
430
    @HostBinding('class.igx-input-group--bootstrap')
431
    public get isTypeBootstrap() {
432
        return this._theme === 'bootstrap';
67,152✔
433
    }
434

435
    /**
436
     * Returns true if the input group theme is Indigo.
437
     * ```typescript
438
     * @ViewChild("MyInputGroup1")
439
     * public inputGroup: IgxInputGroupComponent;
440
     * ngAfterViewInit(){
441
     *    let isTypeIndigo = this.inputGroup.isTypeIndigo;
442
     * }
443
     * ```
444
     */
445
    @HostBinding('class.igx-input-group--indigo')
446
    public get isTypeIndigo() {
447
        return this._theme === 'indigo';
67,152✔
448
    }
449

450
    /**
451
     * Returns whether the input group type is search.
452
     * ```typescript
453
     * @ViewChild("MyInputGroup1")
454
     * public inputGroup: IgxInputGroupComponent;
455
     * ngAfterViewInit(){
456
     *    let isTypeSearch = this.inputGroup.isTypeSearch;
457
     * }
458
     * ```
459
     */
460
    @HostBinding('class.igx-input-group--search')
461
    public get isTypeSearch() {
462
        if(!this.isFileType && !this.input.isTextArea) {
67,162✔
463
            return this.type === 'search';
66,997✔
464
        }
465
    }
466

467
    /** @hidden */
468
    public get filled() {
UNCOV
469
        return this._filled;
×
470
    }
471

472
    /** @hidden */
473
    public set filled(val) {
UNCOV
474
        this._filled = val;
×
475
    }
476

477
    private setComponentTheme() {
478
        if (!this.themeToken.preferToken) {
41,395✔
479
            const theme = getComponentTheme(this.element.nativeElement);
41,395✔
480

481
            if (theme && theme !== this._theme) {
41,395!
UNCOV
482
                this.theme = theme;
×
UNCOV
483
                this.cdr.markForCheck();
×
484
            }
485
        }
486
    }
487

488
    /** @hidden @internal */
489
    public ngAfterContentChecked() {
490
        this.setComponentTheme();
41,395✔
491
    }
492
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc