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

IgniteUI / igniteui-angular / 29587398373

17 Jul 2026 02:17PM UTC coverage: 90.116% (-0.02%) from 90.135%
29587398373

Pull #15125

github

web-flow
Merge ac93ee972 into 8ebabbb38
Pull Request #15125: refactor(*): bundle styles with components

14920 of 17397 branches covered (85.76%)

Branch coverage included in aggregate %.

30030 of 32483 relevant lines covered (92.45%)

34494.72 hits per line

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

87.2
/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,
11
    Input,
12
    QueryList,
13
    booleanAttribute,
14
    inject,
15
    AfterContentChecked,
16
    ChangeDetectionStrategy,
17
    ViewEncapsulation,
18
} from '@angular/core';
19
import { IInputResourceStrings, InputResourceStringsEN } from 'igniteui-angular/core';
20
import { getComponentTheme } from 'igniteui-angular/core';
21
import { IgxButtonDirective } from 'igniteui-angular/directives';
22
import { IgxHintDirective } from './directives-hint/hint.directive';
23
import {
24
    IgxInputDirective,
25
    IgxInputState
26
} from './directives-input/input.directive';
27
import { IgxPrefixDirective } from './directives-prefix/prefix.directive';
28
import { IgxSuffixDirective } from './directives-suffix/suffix.directive';
29

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

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

51
    /**
52
     * Sets the resource strings.
53
     * By default it uses EN resources.
54
     */
55
    @Input()
56
    public set resourceStrings(value: IInputResourceStrings) {
57
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
58
    }
59

60
    /**
61
     * Returns the resource strings.
62
     */
63
    public get resourceStrings(): IInputResourceStrings {
64
        return this._resourceStrings || this._defaultResourceStrings;
105✔
65
    }
66

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

81
    /** @hidden */
82
    @HostBinding('class.igx-input-group--placeholder')
83
    public hasPlaceholder = false;
3,892✔
84

85
    /** @hidden */
86
    @HostBinding('class.igx-input-group--required')
87
    public isRequired = false;
3,892✔
88

89
    /** @hidden */
90
    @HostBinding('class.igx-input-group--focused')
91
    public isFocused = false;
3,892✔
92

93
    /**
94
     * @hidden @internal
95
     * When truthy, disables the input group.
96
     * Controlled by the underlying input.
97
     * ```html
98
     * <igx-input-group [disabled]="true"></igx-input-group>
99
     * ```
100
     */
101
    @HostBinding('class.igx-input-group--disabled')
102
    public disabled = false;
3,892✔
103

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

117
    /** @hidden */
118
    @HostBinding('class.igx-input-group--warning')
119
    public hasWarning = false;
3,892✔
120

121
    /**
122
     * @hidden
123
     * Hints resolved via @ContentChildren — used for standalone input-group usage.
124
     * Kept separate from _externalHints to avoid being overwritten by Angular's
125
     * change detection re-evaluation of @ContentChildren, which caused hints
126
     * projected through wrapper components (e.g. combo) to flicker/disappear.
127
     */
128
    @ContentChildren(IgxHintDirective, { read: IgxHintDirective, descendants: true })
129
    protected _ownHints: QueryList<IgxHintDirective>;
130

131
    /**
132
     * Hints set explicitly by wrapper components (e.g. combo) via the `hints` setter.
133
     * Takes precedence over _ownHints in `hasHints` to avoid CD timing conflicts.
134
     */
135
    private _externalHints: QueryList<IgxHintDirective>;
136

137
    @ContentChildren(IgxPrefixDirective, { read: IgxPrefixDirective, descendants: true })
138
    protected _prefixes: QueryList<IgxPrefixDirective>;
139

140
    @ContentChildren(IgxSuffixDirective, { read: IgxSuffixDirective, descendants: true })
141
    protected _suffixes: QueryList<IgxSuffixDirective>;
142

143
    /** @hidden */
144
    @ContentChild(IgxInputDirective, { read: IgxInputDirective, static: true })
145
    protected input: IgxInputDirective;
146

147
    private _destroyRef = inject(DestroyRef);
3,892✔
148
    private _type: IgxInputGroupType = null;
3,892✔
149
    private _filled = false;
3,892✔
150
    private _theme: IgxTheme;
151
    private _resourceStrings: IInputResourceStrings = null;
3,892✔
152
    private _defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN);
3,892✔
153
    private _readOnly: undefined | boolean;
154

155
    /** @hidden @internal */
156
    @HostBinding('class.igx-input-group--readonly')
157
    public get readOnly(): boolean {
158
        return this._readOnly ?? (this.input?.nativeElement.readOnly || false);
66,196✔
159
    }
160

161
    /** @hidden @internal */
162
    public set readOnly(value: boolean) {
163
        this._readOnly = value;
1,727✔
164
    }
165

166
    /** @hidden */
167
    @HostBinding('class.igx-input-group--valid')
168
    public get validClass(): boolean {
169
        return this.input.valid === IgxInputState.VALID;
66,196✔
170
    }
171

172
    /** @hidden */
173
    @HostBinding('class.igx-input-group--invalid')
174
    public get invalidClass(): boolean {
175
        return this.input.valid === IgxInputState.INVALID;
66,196✔
176
    }
177

178
    /** @hidden */
179
    @HostBinding('class.igx-input-group--filled')
180
    public get isFilled() {
181
        return this._filled || (this.input && this.input.value);
66,305✔
182
    }
183

184
    /** @hidden */
185
    @HostBinding('class.igx-input-group--textarea-group')
186
    public get textAreaClass(): boolean {
187
        return this.input.isTextArea;
66,196✔
188
    }
189

190
    /**
191
     * Sets how the input will be styled.
192
     * Allowed values of type input group type.
193
     * ```html
194
     * <igx-input-group [type]="'search'">
195
     * ```
196
     */
197
    @Input()
198
    public set type(value: IgxInputGroupType) {
199
        this._type = value;
3,051✔
200
    }
201

202
    /**
203
     * Returns the type of the input group. How the input is styled.
204
     * The default is `box`.
205
     * ```typescript
206
     * @ViewChild("MyInputGroup")
207
     * public inputGroup: IgxInputGroupComponent;
208
     * ngAfterViewInit(){
209
     *    let inputType = this.inputGroup.type;
210
     * }
211
     * ```
212
     */
213
    public get type() {
214
        return this._type || this._inputGroupType || 'box';
529,440✔
215
    }
216

217
    /**
218
     * Sets the theme of the input.
219
     * Allowed values of type input group theme.
220
     * ```typescript
221
     * @ViewChild("MyInputGroup")
222
     * public inputGroup: IgxInputGroupComponent;
223
     * ngAfterViewInit() {
224
     *  let inputTheme = 'fluent';
225
     * }
226
     */
227
    @Input()
228
    public set theme(value: IgxTheme) {
229
        this._theme = value;
×
230
    }
231

232
    /**
233
     * Returns the theme of the input.
234
     * The returned value is of type input group type.
235
     * ```typescript
236
     * @ViewChild("MyInputGroup")
237
     * public inputGroup: IgxInputGroupComponent;
238
     * ngAfterViewInit() {
239
     *  let inputTheme = this.inputGroup.theme;
240
     * }
241
     */
242
    public get theme(): IgxTheme {
243
        return this._theme;
×
244
    }
245

246
    constructor() {
247
        this._theme = this.themeToken.theme;
3,892✔
248
        const themeChange = this.themeToken.onChange((theme) => {
3,892✔
249
            if (this._theme !== theme) {
3,892!
250
                this._theme = theme;
×
251
                this.cdr.detectChanges();
×
252
            }
253
        });
254
        this._destroyRef.onDestroy(() => themeChange.unsubscribe());
3,892✔
255
        onResourceChangeHandle(this._destroyRef, () => {
3,892✔
256
            this._defaultResourceStrings = getCurrentResourceStrings(InputResourceStringsEN, false);
549✔
257
        }, this);
258
    }
259

260
    /** @hidden */
261
    @HostListener('click', ['$event'])
262
    public onClick(event: MouseEvent) {
263
        if (
290✔
264
            !this.isFocused &&
660✔
265
            event.target !== this.input.nativeElement &&
266
            !this.suppressInputAutofocus
267
        ) {
268
            this.input.focus();
172✔
269
        }
270
    }
271

272
    /** @hidden */
273
    @HostListener('pointerdown', ['$event'])
274
    public onPointerDown(event: PointerEvent) {
275
        if (this.isFocused && event.target !== this.input.nativeElement) {
19✔
276
            event.preventDefault();
3✔
277
        }
278
    }
279

280
    /** @hidden @internal */
281
    public hintClickHandler(event: MouseEvent) {
282
        event.stopPropagation();
2✔
283
    }
284

285
    /**
286
     * Returns whether the input group has hints.
287
     * ```typescript
288
     * @ViewChild("MyInputGroup")
289
     * public inputGroup: IgxInputGroupComponent;
290
     * ngAfterViewInit(){
291
     *    let inputHints = this.inputGroup.hasHints;
292
     * }
293
     * ```
294
     */
295
    public get hasHints() {
296
        // Prefer externally set hints (from wrapper components like combo)
297
        // over @ContentChildren to avoid CD timing race conditions.
298
        const hints = this._externalHints ?? this._ownHints;
66,195✔
299
        return hints?.length > 0;
66,195✔
300
    }
301

302
    /** @hidden @internal */
303
    public set hints(items: QueryList<IgxHintDirective>) {
304
        this._externalHints = items;
18,974✔
305
    }
306

307
    /** @hidden @internal */
308
    @HostBinding('class.igx-input-group--prefixed')
309
    public get hasPrefixes() {
310
        return this._prefixes.length > 0;
66,196✔
311
    }
312

313
    /** @hidden @internal */
314
    public set prefixes(items: QueryList<IgxPrefixDirective>) {
315
        this._prefixes = items;
869✔
316
    }
317

318
    /** @hidden @internal */
319
    @HostBinding('class.igx-input-group--suffixed')
320
    public get hasSuffixes() {
321
        return this._suffixes.length > 0 || (this.isFileType && this.isFilled && !this.disabled);
66,196✔
322
    }
323

324
    /** @hidden @internal */
325
    public set suffixes(items: QueryList<IgxSuffixDirective>) {
326
        this._suffixes = items;
18,423✔
327
    }
328

329
    /**
330
     * Returns whether the input group has border.
331
     * ```typescript
332
     * @ViewChild("MyInputGroup")
333
     * public inputGroup: IgxInputGroupComponent;
334
     * ngAfterViewInit(){
335
     *    let inputBorder = this.inputGroup.hasBorder;
336
     * }
337
     * ```
338
     */
339
    public get hasBorder() {
340
        return (
×
341
            (this.type === 'line' || this.type === 'box') &&
×
342
            this._theme === 'material'
343
        );
344
    }
345

346
    /**
347
     * Returns whether the input group type is line.
348
     * ```typescript
349
     * @ViewChild("MyInputGroup1")
350
     * public inputGroup: IgxInputGroupComponent;
351
     * ngAfterViewInit(){
352
     *    let isTypeLine = this.inputGroup.isTypeLine;
353
     * }
354
     * ```
355
     */
356
    @HostBinding('class.igx-input-group--line')
357
    public get isTypeLine(): boolean {
358
        return this.type === 'line' && this._theme === 'material';
66,206✔
359
    }
360

361
    /** @hidden @internal */
362
    @HostBinding('class.igx-input-group--base')
363
    public get isNotBorder(): boolean {
364
        return this.type !== 'border' && this._theme === 'material';
66,196✔
365
    }
366

367
    /**
368
     * Returns whether the input group type is box.
369
     * ```typescript
370
     * @ViewChild("MyInputGroup1")
371
     * public inputGroup: IgxInputGroupComponent;
372
     * ngAfterViewInit(){
373
     *    let isTypeBox = this.inputGroup.isTypeBox;
374
     * }
375
     * ```
376
     */
377
    @HostBinding('class.igx-input-group--box')
378
    public get isTypeBox() {
379
        return this.type === 'box' && this._theme === 'material';
66,206✔
380
    }
381

382
    /** @hidden @internal */
383
    public clearValueHandler() {
384
        this.input.clear();
2✔
385
    }
386

387
    /** @hidden @internal */
388
    @HostBinding('class.igx-input-group--file')
389
    public get isFileType() {
390
        return this.input.type === 'file';
555,110✔
391
    }
392

393
    /** @hidden @internal */
394
    @HostBinding('class.igx-file-input')
395
    public get isFileInput() {
396
        return this.input.type === 'file';
66,196✔
397
    }
398

399
    /** @hidden @internal */
400
    @HostBinding('class.igx-file-input--filled')
401
    public get isFileInputFilled() {
402
        return this.isFileType && this.isFilled;
66,196✔
403
    }
404

405
    /** @hidden @internal */
406
    @HostBinding('class.igx-file-input--focused')
407
    public get isFileInputFocused() {
408
        return this.isFileType && this.isFocused;
66,196✔
409
    }
410

411
    /** @hidden @internal */
412
    @HostBinding('class.igx-file-input--disabled')
413
    public get isFileInputDisabled() {
414
        return this.isFileType && this.disabled;
66,196✔
415
    }
416

417
    /** @hidden @internal */
418
    public get fileNames() {
419
        return this.input.fileNames || this.resourceStrings.igx_input_file_placeholder;
70✔
420
    }
421

422
    /**
423
     * Returns whether the input group type is border.
424
     * ```typescript
425
     * @ViewChild("MyInputGroup1")
426
     * public inputGroup: IgxInputGroupComponent;
427
     * ngAfterViewInit(){
428
     *    let isTypeBorder = this.inputGroup.isTypeBorder;
429
     * }
430
     * ```
431
     */
432
    @HostBinding('class.igx-input-group--border')
433
    public get isTypeBorder() {
434
        return this.type === 'border' && this._theme === 'material';
264,791✔
435
    }
436

437
    /**
438
     * Returns true if the input group theme is Fluent.
439
     * ```typescript
440
     * @ViewChild("MyInputGroup1")
441
     * public inputGroup: IgxInputGroupComponent;
442
     * ngAfterViewInit(){
443
     *    let isTypeFluent = this.inputGroup.isTypeFluent;
444
     * }
445
     * ```
446
     */
447
    @HostBinding('class.igx-input-group--fluent')
448
    public get isTypeFluent() {
449
        return this._theme === 'fluent';
66,196✔
450
    }
451

452
    /**
453
     * Returns true if the input group theme is Bootstrap.
454
     * ```typescript
455
     * @ViewChild("MyInputGroup1")
456
     * public inputGroup: IgxInputGroupComponent;
457
     * ngAfterViewInit(){
458
     *    let isTypeBootstrap = this.inputGroup.isTypeBootstrap;
459
     * }
460
     * ```
461
     */
462
    @HostBinding('class.igx-input-group--bootstrap')
463
    public get isTypeBootstrap() {
464
        return this._theme === 'bootstrap';
66,196✔
465
    }
466

467
    /**
468
     * Returns true if the input group theme is Indigo.
469
     * ```typescript
470
     * @ViewChild("MyInputGroup1")
471
     * public inputGroup: IgxInputGroupComponent;
472
     * ngAfterViewInit(){
473
     *    let isTypeIndigo = this.inputGroup.isTypeIndigo;
474
     * }
475
     * ```
476
     */
477
    @HostBinding('class.igx-input-group--indigo')
478
    public get isTypeIndigo() {
479
        return this._theme === 'indigo';
66,196✔
480
    }
481

482
    /**
483
     * Returns whether the input group type is search.
484
     * ```typescript
485
     * @ViewChild("MyInputGroup1")
486
     * public inputGroup: IgxInputGroupComponent;
487
     * ngAfterViewInit(){
488
     *    let isTypeSearch = this.inputGroup.isTypeSearch;
489
     * }
490
     * ```
491
     */
492
    @HostBinding('class.igx-input-group--search')
493
    public get isTypeSearch() {
494
        if(!this.isFileType && !this.input.isTextArea) {
66,206✔
495
            return this.type === 'search';
66,041✔
496
        }
497
    }
498

499
    /** @hidden */
500
    public get filled() {
501
        return this._filled;
×
502
    }
503

504
    /** @hidden */
505
    public set filled(val) {
506
        this._filled = val;
×
507
    }
508

509
    private setComponentTheme() {
510
        if (!this.themeToken.preferToken) {
40,436✔
511
            const theme = getComponentTheme(this.element.nativeElement);
40,436✔
512

513
            if (theme && theme !== this._theme) {
40,436!
514
                this.theme = theme;
×
515
                this.cdr.markForCheck();
×
516
            }
517
        }
518
    }
519

520
    /** @hidden @internal */
521
    public ngAfterContentChecked() {
522
        this.setComponentTheme();
40,436✔
523
    }
524
}
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