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

IgniteUI / igniteui-angular / 17823480788

18 Sep 2025 08:56AM UTC coverage: 91.514% (-0.002%) from 91.516%
17823480788

Pull #16102

github

web-flow
Merge 3073c964b into 2a0fadc67
Pull Request #16102: refactor(tree): add raf check for tree SSR

13520 of 15849 branches covered (85.31%)

4 of 5 new or added lines in 1 file covered. (80.0%)

9 existing lines in 2 files now uncovered.

27242 of 29768 relevant lines covered (91.51%)

34605.65 hits per line

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

90.57
/projects/igniteui-angular/src/lib/directives/input/input.directive.ts
1
import {
2
    AfterViewInit,
3
    ChangeDetectorRef,
4
    Directive,
5
    ElementRef,
6
    HostBinding,
7
    HostListener,
8
    Inject,
9
    Input,
10
    OnDestroy,
11
    Optional,
12
    Renderer2,
13
    Self,
14
    booleanAttribute,
15
} from '@angular/core';
16
import {
17
    AbstractControl,
18
    NgControl,
19
    NgModel
20
} from '@angular/forms';
21
import { Subscription } from 'rxjs';
22
import { IgxInputGroupBase } from '../../input-group/input-group.common';
23

24
const nativeValidationAttributes = [
3✔
25
    'required',
26
    'pattern',
27
    'minlength',
28
    'maxlength',
29
    'min',
30
    'max',
31
    'step',
32
];
33

34
export enum IgxInputState {
3✔
35
    INITIAL,
3✔
36
    VALID,
3✔
37
    INVALID,
3✔
38
}
39

40
/**
41
 * The `igxInput` directive creates single- or multiline text elements, covering common scenarios when dealing with form inputs.
42
 *
43
 * @igxModule IgxInputGroupModule
44
 *
45
 * @igxParent Data Entry & Display
46
 *
47
 * @igxTheme igx-input-group-theme
48
 *
49
 * @igxKeywords input, input group, form, field, validation
50
 *
51
 * @igxGroup presentation
52
 *
53
 * @example
54
 * ```html
55
 * <input-group>
56
 *  <label for="address">Address</label>
57
 *  <input igxInput name="address" type="text" [(ngModel)]="customer.address">
58
 * </input-group>
59
 * ```
60
 */
61
@Directive({
62
    selector: '[igxInput]',
63
    exportAs: 'igxInput',
64
    standalone: true
65
})
66
export class IgxInputDirective implements AfterViewInit, OnDestroy {
3✔
67
    /**
68
     * Sets/gets whether the `"igx-input-group__input"` class is added to the host element.
69
     * Default value is `false`.
70
     *
71
     * @example
72
     * ```typescript
73
     * this.igxInput.isInput = true;
74
     * ```
75
     *
76
     * @example
77
     * ```typescript
78
     * let isCLassAdded = this.igxInput.isInput;
79
     * ```
80
     */
81
    @HostBinding('class.igx-input-group__input')
82
    public isInput = false;
3,574✔
83
    /**
84
     * Sets/gets whether the `"class.igx-input-group__textarea"` class is added to the host element.
85
     * Default value is `false`.
86
     *
87
     * @example
88
     * ```typescript
89
     * this.igxInput.isTextArea = true;
90
     * ```
91
     *
92
     * @example
93
     * ```typescript
94
     * let isCLassAdded = this.igxInput.isTextArea;
95
     * ```
96
     */
97
    @HostBinding('class.igx-input-group__textarea')
98
    public isTextArea = false;
3,574✔
99

100
    private _valid = IgxInputState.INITIAL;
3,574✔
101
    private _statusChanges$: Subscription;
102
    private _valueChanges$: Subscription;
103
    private _fileNames: string;
104
    private _disabled = false;
3,574✔
105

106
    constructor(
107
        public inputGroup: IgxInputGroupBase,
3,574✔
108
        @Optional() @Self() @Inject(NgModel) protected ngModel: NgModel,
3,574✔
109
        @Optional()
110
        @Self()
111
        @Inject(NgControl)
112
        protected formControl: NgControl,
3,574✔
113
        protected element: ElementRef<HTMLInputElement>,
3,574✔
114
        protected cdr: ChangeDetectorRef,
3,574✔
115
        protected renderer: Renderer2
3,574✔
116
    ) { }
117

118
    private get ngControl(): NgControl {
119
        return this.ngModel ? this.ngModel : this.formControl;
48,862✔
120
    }
121

122
    /**
123
     * Sets the `value` property.
124
     *
125
     * @example
126
     * ```html
127
     * <input-group>
128
     *  <input igxInput #igxInput [value]="'IgxInput Value'">
129
     * </input-group>
130
     * ```
131
     */
132
    @Input()
133
    public set value(value: any) {
134
        this.nativeElement.value = value ?? '';
2,931✔
135
        this.updateValidityState();
2,931✔
136
    }
137
    /**
138
     * Gets the `value` property.
139
     *
140
     * @example
141
     * ```typescript
142
     * @ViewChild('igxInput', {read: IgxInputDirective})
143
     *  public igxInput: IgxInputDirective;
144
     * let inputValue = this.igxInput.value;
145
     * ```
146
     */
147
    public get value() {
148
        return this.nativeElement.value;
85,251✔
149
    }
150
    /**
151
     * Sets the `disabled` property.
152
     *
153
     * @example
154
     * ```html
155
     * <input-group>
156
     *  <input igxInput #igxInput [disabled]="true">
157
     * </input-group>
158
     * ```
159
     */
160
    @Input({ transform: booleanAttribute })
161
    @HostBinding('disabled')
162
    public set disabled(value: boolean) {
163
        this._disabled = this.inputGroup.disabled = value;
3,990✔
164
        if (this.focused && this._disabled) {
3,990✔
165
            // Browser focus may not fire in good time and mess with change detection, adjust here in advance:
166
            this.inputGroup.isFocused = false;
19✔
167
        }
168
    }
169
    /**
170
     * Gets the `disabled` property
171
     *
172
     * @example
173
     * ```typescript
174
     * @ViewChild('igxInput', {read: IgxInputDirective})
175
     *  public igxInput: IgxInputDirective;
176
     * let isDisabled = this.igxInput.disabled;
177
     * ```
178
     */
179
    public get disabled() {
180
        return this._disabled;
71,147✔
181
    }
182

183
    /**
184
     * Sets the `required` property.
185
     *
186
     * @example
187
     * ```html
188
     * <input-group>
189
     *  <input igxInput #igxInput required>
190
     * </input-group>
191
     * ```
192
     */
193
    @Input({ transform: booleanAttribute })
194
    public set required(value: boolean) {
195
        this.nativeElement.required = this.inputGroup.isRequired = value;
44✔
196
    }
197

198
    /**
199
     * Gets whether the igxInput is required.
200
     *
201
     * @example
202
     * ```typescript
203
     * let isRequired = this.igxInput.required;
204
     * ```
205
     */
206
    public get required() {
207
        let validation;
208
        if (this.ngControl && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) {
9,152✔
209
            validation = this.ngControl.control.validator({} as AbstractControl);
768✔
210
        }
211
        return validation && validation.required || this.nativeElement.hasAttribute('required');
9,152✔
212
    }
213
    /**
214
     * @hidden
215
     * @internal
216
     */
217
    @HostListener('focus')
218
    public onFocus() {
219
        this.inputGroup.isFocused = true;
1,055✔
220
    }
221
    /**
222
     * @param event The event to invoke the handler
223
     *
224
     * @hidden
225
     * @internal
226
     */
227
    @HostListener('blur')
228
    public onBlur() {
229
        this.inputGroup.isFocused = false;
569✔
230
        if (this.ngControl?.control) {
569✔
231
            this.ngControl.control.markAsTouched();
287✔
232
        }
233
        this.updateValidityState();
569✔
234
    }
235
    /** @hidden @internal */
236
    @HostListener('input')
237
    public onInput() {
238
        this.checkNativeValidity();
485✔
239
    }
240
    /** @hidden @internal */
241
    @HostListener('change', ['$event'])
242
    public change(event: Event) {
243
        if (this.type === 'file') {
1!
UNCOV
244
            const fileList: FileList | null = (event.target as HTMLInputElement)
×
245
                .files;
246
            const fileArray: File[] = [];
×
247

UNCOV
248
            if (fileList) {
×
UNCOV
249
                for (const file of Array.from(fileList)) {
×
UNCOV
250
                    fileArray.push(file);
×
251
                }
252
            }
253

254
            this._fileNames = (fileArray || []).map((f: File) => f.name).join(', ');
×
255

UNCOV
256
            if (this.required && fileList?.length > 0) {
×
UNCOV
257
                this._valid = IgxInputState.INITIAL;
×
258
            }
259
        }
260
    }
261

262
    /** @hidden @internal */
263
    public get fileNames() {
264
        return this._fileNames;
70✔
265
    }
266

267
    /** @hidden @internal */
268
    public clear() {
269
        this.ngControl?.control?.setValue('');
2✔
270
        this.nativeElement.value = null;
2✔
271
        this._fileNames = '';
2✔
272
    }
273

274
    /** @hidden @internal */
275
    public ngAfterViewInit() {
276
        this.inputGroup.hasPlaceholder = this.nativeElement.hasAttribute(
3,574✔
277
            'placeholder'
278
        );
279

280
        if (this.ngControl && this.ngControl.disabled !== null) {
3,574✔
281
            this.disabled = this.ngControl.disabled;
1,514✔
282
        }
283
        this.inputGroup.disabled =
3,574✔
284
            this.inputGroup.disabled ||
7,143✔
285
            this.nativeElement.hasAttribute('disabled');
286
        this.inputGroup.isRequired = this.nativeElement.hasAttribute(
3,574✔
287
            'required'
288
        );
289

290
        // Make sure we do not invalidate the input on init
291
        if (!this.ngControl) {
3,574✔
292
            this._valid = IgxInputState.INITIAL;
2,060✔
293
        }
294
        // Also check the control's validators for required
295
        if (this.required && !this.inputGroup.isRequired) {
3,574✔
296
            this.inputGroup.isRequired = this.required;
70✔
297
        }
298

299
        this.renderer.setAttribute(this.nativeElement, 'aria-required', this.required.toString());
3,574✔
300

301
        const elTag = this.nativeElement.tagName.toLowerCase();
3,574✔
302
        if (elTag === 'textarea') {
3,574✔
303
            this.isTextArea = true;
13✔
304
        } else {
305
            this.isInput = true;
3,561✔
306
        }
307

308
        if (this.ngControl) {
3,574✔
309
            this._statusChanges$ = this.ngControl.statusChanges.subscribe(
1,514✔
310
                this.onStatusChanged.bind(this)
311
            );
312

313
            this._valueChanges$ = this.ngControl.valueChanges.subscribe(
1,514✔
314
                this.onValueChanged.bind(this)
315
            );
316
        }
317

318
        this.cdr.detectChanges();
3,574✔
319
    }
320
    /** @hidden @internal */
321
    public ngOnDestroy() {
322
        if (this._statusChanges$) {
3,552✔
323
            this._statusChanges$.unsubscribe();
1,514✔
324
        }
325

326
        if (this._valueChanges$) {
3,552✔
327
            this._valueChanges$.unsubscribe();
1,514✔
328
        }
329
    }
330
    /**
331
     * Sets a focus on the igxInput.
332
     *
333
     * @example
334
     * ```typescript
335
     * this.igxInput.focus();
336
     * ```
337
     */
338
    public focus() {
339
        this.nativeElement.focus();
287✔
340
    }
341
    /**
342
     * Gets the `nativeElement` of the igxInput.
343
     *
344
     * @example
345
     * ```typescript
346
     * let igxInputNativeElement = this.igxInput.nativeElement;
347
     * ```
348
     */
349
    public get nativeElement() {
350
        return this.element.nativeElement;
750,587✔
351
    }
352
    /** @hidden @internal */
353
    protected onStatusChanged() {
354
        // Enable/Disable control based on ngControl #7086
355
        if (this.disabled !== this.ngControl.disabled) {
1,636✔
356
            this.disabled = this.ngControl.disabled;
137✔
357
        }
358
        this.updateValidityState();
1,636✔
359
    }
360

361
    /** @hidden @internal */
362
    protected onValueChanged() {
363
        if (this._fileNames && !this.value) {
1,634!
UNCOV
364
            this._fileNames = '';
×
365
        }
366
    }
367

368
    /**
369
     * @hidden
370
     * @internal
371
     */
372
    protected updateValidityState() {
373
        if (this.ngControl) {
5,136✔
374
            if (!this.disabled && this.isTouchedOrDirty) {
1,930✔
375
                if (this.hasValidators) {
762✔
376
                    // Run the validation with empty object to check if required is enabled.
377
                    const error = this.ngControl.control.validator({} as AbstractControl);
285✔
378
                    this.inputGroup.isRequired = error && error.required;
285✔
379
                    if (this.focused) {
285✔
380
                        this._valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
91✔
381
                    } else {
382
                        this._valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
194✔
383
                    }
384
                } else {
385
                    // If validator is dynamically cleared, reset label's required class(asterisk) and IgxInputState #10010
386
                    this.inputGroup.isRequired = false;
477✔
387
                    this._valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
477✔
388
                }
389
            } else {
390
                this._valid = IgxInputState.INITIAL;
1,168✔
391
            }
392
            this.renderer.setAttribute(this.nativeElement, 'aria-required', this.required.toString());
1,930✔
393
            const ariaInvalid = this.valid === IgxInputState.INVALID;
1,930✔
394
            this.renderer.setAttribute(this.nativeElement, 'aria-invalid', ariaInvalid.toString());
1,930✔
395
        } else {
396
            this.checkNativeValidity();
3,206✔
397
        }
398
    }
399

400
    private get isTouchedOrDirty(): boolean {
401
        return (this.ngControl.control.touched || this.ngControl.control.dirty);
1,684✔
402
    }
403

404
    private get hasValidators(): boolean {
405
        return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
762✔
406
    }
407

408
    /**
409
     * Gets whether the igxInput has a placeholder.
410
     *
411
     * @example
412
     * ```typescript
413
     * let hasPlaceholder = this.igxInput.hasPlaceholder;
414
     * ```
415
     */
416
    public get hasPlaceholder() {
417
        return this.nativeElement.hasAttribute('placeholder');
1✔
418
    }
419
    /**
420
     * Gets the placeholder element of the igxInput.
421
     *
422
     * @example
423
     * ```typescript
424
     * let igxInputPlaceholder = this.igxInput.placeholder;
425
     * ```
426
     */
427
    public get placeholder() {
428
        return this.nativeElement.placeholder;
4✔
429
    }
430

431
    /**
432
     * @returns An indicator of whether the input has validator attributes or not
433
     *
434
     * @hidden
435
     * @internal
436
     */
437
    private _hasValidators(): boolean {
438
        for (const nativeValidationAttribute of nativeValidationAttributes) {
3,688✔
439
            if (this.nativeElement.hasAttribute(nativeValidationAttribute)) {
25,342✔
440
                return true;
95✔
441
            }
442
        }
443
        return false;
3,593✔
444
    }
445

446
    /**
447
     * Gets whether the igxInput is focused.
448
     *
449
     * @example
450
     * ```typescript
451
     * let isFocused = this.igxInput.focused;
452
     * ```
453
     */
454
    public get focused() {
455
        return this.inputGroup.isFocused;
4,401✔
456
    }
457
    /**
458
     * Gets the state of the igxInput.
459
     *
460
     * @example
461
     * ```typescript
462
     * let igxInputState = this.igxInput.valid;
463
     * ```
464
     */
465
    public get valid(): IgxInputState {
466
        return this._valid;
129,831✔
467
    }
468

469
    /**
470
     * Sets the state of the igxInput.
471
     *
472
     * @example
473
     * ```typescript
474
     * this.igxInput.valid = IgxInputState.INVALID;
475
     * ```
476
     */
477
    public set valid(value: IgxInputState) {
478
        this._valid = value;
4,843✔
479
    }
480

481
    /**
482
     * Gets whether the igxInput is valid.
483
     *
484
     * @example
485
     * ```typescript
486
     * let valid = this.igxInput.isValid;
487
     * ```
488
     */
489
    public get isValid(): boolean {
UNCOV
490
        return this.valid !== IgxInputState.INVALID;
×
491
    }
492

493
    /**
494
     * A function to assign a native validity property of an input.
495
     * This should be used when there's no ngControl
496
     *
497
     * @hidden
498
     * @internal
499
     */
500
    private checkNativeValidity() {
501
        if (!this.disabled && this._hasValidators()) {
3,691✔
502
            this._valid = this.nativeElement.checkValidity() ?
95✔
503
                this.focused ? IgxInputState.VALID : IgxInputState.INITIAL :
80✔
504
                IgxInputState.INVALID;
505
        }
506
    }
507

508
    /**
509
     * Returns the input type.
510
     *
511
     * @hidden
512
     * @internal
513
     */
514
    public get type() {
515
        return this.nativeElement.type;
598,816✔
516
    }
517
}
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