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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

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

73.08
/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 = [
2✔
25
    'required',
26
    'pattern',
27
    'minlength',
28
    'maxlength',
29
    'min',
30
    'max',
31
    'step',
32
];
33

34
export enum IgxInputState {
2✔
35
    INITIAL,
2✔
36
    VALID,
2✔
37
    INVALID,
2✔
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 {
2✔
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;
205✔
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;
205✔
99

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

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

118
    private get ngControl(): NgControl {
119
        return this.ngModel ? this.ngModel : this.formControl;
2,629✔
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 ?? '';
190!
135
        this.updateValidityState();
190✔
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;
4,426✔
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;
344✔
164
        if (this.focused && this._disabled) {
344!
165
            // Browser focus may not fire in good time and mess with change detection, adjust here in advance:
UNCOV
166
            this.inputGroup.isFocused = false;
×
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;
4,185✔
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) {
UNCOV
195
        this.nativeElement.required = this.inputGroup.isRequired = value;
×
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)) {
533✔
209
            validation = this.ngControl.control.validator({} as AbstractControl);
6✔
210
        }
211
        return validation && validation.required || this.nativeElement.hasAttribute('required');
533!
212
    }
213
    /**
214
     * @hidden
215
     * @internal
216
     */
217
    @HostListener('focus')
218
    public onFocus() {
219
        this.inputGroup.isFocused = true;
45✔
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;
22✔
230
        this.updateValidityState();
22✔
231
    }
232
    /** @hidden @internal */
233
    @HostListener('input')
234
    public onInput() {
235
        this.checkNativeValidity();
16✔
236
    }
237
    /** @hidden @internal */
238
    @HostListener('change', ['$event'])
239
    public change(event: Event) {
UNCOV
240
        if (this.type === 'file') {
×
241
            const fileList: FileList | null = (event.target as HTMLInputElement)
×
242
                .files;
243
            const fileArray: File[] = [];
×
244

245
            if (fileList) {
×
246
                for (const file of Array.from(fileList)) {
×
247
                    fileArray.push(file);
×
248
                }
249
            }
250

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

253
            if (this.required && fileList?.length > 0) {
×
254
                this._valid = IgxInputState.INITIAL;
×
255
            }
256
        }
257
    }
258

259
    /** @hidden @internal */
260
    public get fileNames() {
UNCOV
261
        return this._fileNames;
×
262
    }
263

264
    /** @hidden @internal */
265
    public clear() {
UNCOV
266
        this.ngControl?.control?.setValue('');
×
UNCOV
267
        this.nativeElement.value = null;
×
UNCOV
268
        this._fileNames = '';
×
269
    }
270

271
    /** @hidden @internal */
272
    public ngAfterViewInit() {
273
        this.inputGroup.hasPlaceholder = this.nativeElement.hasAttribute(
205✔
274
            'placeholder'
275
        );
276

277
        if (this.ngControl && this.ngControl.disabled !== null) {
205✔
278
            this.disabled = this.ngControl.disabled;
69✔
279
        }
280
        this.inputGroup.disabled =
205✔
281
            this.inputGroup.disabled ||
410✔
282
            this.nativeElement.hasAttribute('disabled');
283
        this.inputGroup.isRequired = this.nativeElement.hasAttribute(
205✔
284
            'required'
285
        );
286

287
        // Make sure we do not invalidate the input on init
288
        if (!this.ngControl) {
205✔
289
            this._valid = IgxInputState.INITIAL;
136✔
290
        }
291
        // Also check the control's validators for required
292
        if (this.required && !this.inputGroup.isRequired) {
205!
UNCOV
293
            this.inputGroup.isRequired = this.required;
×
294
        }
295

296
        this.renderer.setAttribute(this.nativeElement, 'aria-required', this.required.toString());
205✔
297

298
        const elTag = this.nativeElement.tagName.toLowerCase();
205✔
299
        if (elTag === 'textarea') {
205!
UNCOV
300
            this.isTextArea = true;
×
301
        } else {
302
            this.isInput = true;
205✔
303
        }
304

305
        if (this.ngControl) {
205✔
306
            this._statusChanges$ = this.ngControl.statusChanges.subscribe(
69✔
307
                this.onStatusChanged.bind(this)
308
            );
309

310
            this._valueChanges$ = this.ngControl.valueChanges.subscribe(
69✔
311
                this.onValueChanged.bind(this)
312
            );
313
        }
314

315
        this.cdr.detectChanges();
205✔
316
    }
317
    /** @hidden @internal */
318
    public ngOnDestroy() {
319
        if (this._statusChanges$) {
205✔
320
            this._statusChanges$.unsubscribe();
69✔
321
        }
322

323
        if (this._valueChanges$) {
205✔
324
            this._valueChanges$.unsubscribe();
69✔
325
        }
326
    }
327
    /**
328
     * Sets a focus on the igxInput.
329
     *
330
     * @example
331
     * ```typescript
332
     * this.igxInput.focus();
333
     * ```
334
     */
335
    public focus() {
336
        this.nativeElement.focus();
18✔
337
    }
338
    /**
339
     * Gets the `nativeElement` of the igxInput.
340
     *
341
     * @example
342
     * ```typescript
343
     * let igxInputNativeElement = this.igxInput.nativeElement;
344
     * ```
345
     */
346
    public get nativeElement() {
347
        return this.element.nativeElement;
31,404✔
348
    }
349
    /** @hidden @internal */
350
    protected onStatusChanged() {
351
        // Enable/Disable control based on ngControl #7086
352
        if (this.disabled !== this.ngControl.disabled) {
123✔
353
            this.disabled = this.ngControl.disabled;
23✔
354
        }
355
        this.updateValidityState();
123✔
356
    }
357

358
    /** @hidden @internal */
359
    protected onValueChanged() {
360
        if (this._fileNames && !this.value) {
123!
361
            this._fileNames = '';
×
362
        }
363
    }
364

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

397
    private get isTouchedOrDirty(): boolean {
398
        return (this.ngControl.control.touched || this.ngControl.control.dirty);
77✔
399
    }
400

401
    private get hasValidators(): boolean {
402
        return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
16✔
403
    }
404

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

428
    /**
429
     * @returns An indicator of whether the input has validator attributes or not
430
     *
431
     * @hidden
432
     * @internal
433
     */
434
    private _hasValidators(): boolean {
435
        for (const nativeValidationAttribute of nativeValidationAttributes) {
227✔
436
            if (this.nativeElement.hasAttribute(nativeValidationAttribute)) {
1,589!
UNCOV
437
                return true;
×
438
            }
439
        }
440
        return false;
227✔
441
    }
442

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

466
    /**
467
     * Sets the state of the igxInput.
468
     *
469
     * @example
470
     * ```typescript
471
     * this.igxInput.valid = IgxInputState.INVALID;
472
     * ```
473
     */
474
    public set valid(value: IgxInputState) {
475
        this._valid = value;
321✔
476
    }
477

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

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

505
    /**
506
     * Returns the input type.
507
     *
508
     * @hidden
509
     * @internal
510
     */
511
    public get type() {
512
        return this.nativeElement.type;
23,240✔
513
    }
514
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc