• 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

1.19
/projects/igniteui-angular/src/lib/directives/autocomplete/autocomplete.directive.ts
1
import {
2
    ChangeDetectorRef,
3
    Directive,
4
    ElementRef,
5
    EventEmitter,
6
    HostBinding,
7
    HostListener,
8
    Inject,
9
    Input,
10
    OnDestroy,
11
    Optional,
12
    Output,
13
    Self,
14
    AfterViewInit,
15
    OnInit,
16
    booleanAttribute
17
} from '@angular/core';
18
import { NgModel, FormControlName } from '@angular/forms';
19
import { Subject } from 'rxjs';
20
import { takeUntil } from 'rxjs/operators';
21
import { CancelableEventArgs, IBaseEventArgs } from '../../core/utils';
22
import {
23
    AbsoluteScrollStrategy,
24
    AutoPositionStrategy,
25
    IPositionStrategy,
26
    IScrollStrategy,
27
    OverlaySettings
28
} from '../../services/public_api';
29
import {
30
    IgxDropDownComponent
31
} from '../../drop-down/drop-down.component';
32
import { IgxDropDownItemNavigationDirective } from '../../drop-down/drop-down-navigation.directive';
33
import { IgxInputGroupComponent } from '../../input-group/public_api';
34
import { IgxOverlayOutletDirective } from '../toggle/toggle.directive';
35
import { ISelectionEventArgs } from '../../drop-down/drop-down.common';
36

37
/**
38
 * Interface that encapsulates onItemSelection event arguments - new value and cancel selection.
39
 *
40
 * @export
41
 */
42
export interface AutocompleteSelectionChangingEventArgs extends CancelableEventArgs, IBaseEventArgs {
43
    /**
44
     * New value selected from the drop down
45
     */
46
    value: string;
47
}
48

49
export interface AutocompleteOverlaySettings {
50
    /** Position strategy to use with this settings */
51
    positionStrategy?: IPositionStrategy;
52
    /** Scroll strategy to use with this settings */
53
    scrollStrategy?: IScrollStrategy;
54
    /** Set the outlet container to attach the overlay to */
55
    outlet?: IgxOverlayOutletDirective | ElementRef;
56
}
57

58
/**
59
 * **Ignite UI for Angular Autocomplete** -
60
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/autocomplete.html)
61
 *
62
 * The igxAutocomplete directive provides a way to enhance a text input
63
 * by showing a drop down of suggested options, provided by the developer.
64
 *
65
 * Example:
66
 * ```html
67
 * <input type="text" [igxAutocomplete]="townsPanel" #autocompleteRef="igxAutocomplete"/>
68
 * <igx-drop-down #townsPanel>
69
 *     <igx-drop-down-item *ngFor="let town of towns | startsWith:townSelected" [value]="town">
70
 *         {{town}}
71
 *     </igx-drop-down-item>
72
 * </igx-drop-down>
73
 * ```
74
 */
75
@Directive({
76
    selector: '[igxAutocomplete]',
77
    exportAs: 'igxAutocomplete',
78
    standalone: true
79
})
80
export class IgxAutocompleteDirective extends IgxDropDownItemNavigationDirective implements OnDestroy, AfterViewInit, OnInit {
2✔
81
    /**
82
     * Sets the target of the autocomplete directive
83
     *
84
     * ```html
85
     * <!-- Set -->
86
     * <input [igxAutocomplete]="dropdown" />
87
     * ...
88
     * <igx-drop-down #dropdown>
89
     * ...
90
     * </igx-drop-down>
91
     * ```
92
     */
93
    @Input('igxAutocomplete')
94
    public override get target(): IgxDropDownComponent {
UNCOV
95
        return this._target as IgxDropDownComponent;
×
96
    }
97
    public override set target(v: IgxDropDownComponent) {
UNCOV
98
        this._target = v;
×
99
    }
100

101
    /**
102
     * Provide overlay settings for the autocomplete drop down
103
     *
104
     * ```typescript
105
     * // get
106
     * let settings = this.autocomplete.autocompleteSettings;
107
     * ```
108
     * ```html
109
     * <!--set-->
110
     * <input type="text" [igxAutocomplete]="townsPanel" [igxAutocompleteSettings]="settings"/>
111
     * ```
112
     * ```typescript
113
     * // set
114
     * this.settings = {
115
     *  positionStrategy: new ConnectedPositioningStrategy({
116
     *      closeAnimation: null,
117
     *      openAnimation: null
118
     *  })
119
     * };
120
     * ```
121
     */
122
    @Input('igxAutocompleteSettings')
123
    public autocompleteSettings: AutocompleteOverlaySettings;
124

125
    /** @hidden @internal */
126
    @HostBinding('attr.autocomplete')
UNCOV
127
    public autofill = 'off';
×
128

129
    /** @hidden  @internal */
130
    @HostBinding('attr.role')
UNCOV
131
    public role = 'combobox';
×
132

133
    /**
134
     * Enables/disables autocomplete component
135
     *
136
     * ```typescript
137
     * // get
138
     * let disabled = this.autocomplete.disabled;
139
     * ```
140
     * ```html
141
     * <!--set-->
142
     * <input type="text" [igxAutocomplete]="townsPanel" [igxAutocompleteDisabled]="disabled"/>
143
     * ```
144
     * ```typescript
145
     * // set
146
     * public disabled = true;
147
     * ```
148
     */
149
    @Input({ alias: 'igxAutocompleteDisabled', transform: booleanAttribute })
UNCOV
150
    public disabled = false;
×
151

152
    /**
153
     * Emitted after item from the drop down is selected
154
     *
155
     * ```html
156
     * <input igxInput [igxAutocomplete]="townsPanel" (selectionChanging)='selectionChanging($event)' />
157
     * ```
158
     */
159
    @Output()
UNCOV
160
    public selectionChanging = new EventEmitter<AutocompleteSelectionChangingEventArgs>();
×
161

162
    /** @hidden @internal */
163
    public get nativeElement(): HTMLInputElement {
UNCOV
164
        return this.elementRef.nativeElement;
×
165
    }
166

167
    /** @hidden @internal */
168
    public get parentElement(): HTMLElement {
UNCOV
169
        return this.group ? this.group.element.nativeElement : this.nativeElement;
×
170
    }
171

172
    private get settings(): OverlaySettings {
UNCOV
173
        const settings = Object.assign({}, this.defaultSettings, this.autocompleteSettings);
×
UNCOV
174
        if (!settings.target) {
×
175
            const positionStrategyClone: IPositionStrategy = settings.positionStrategy.clone();
×
176
            settings.target = this.parentElement;
×
177
            settings.positionStrategy = positionStrategyClone;
×
178
        }
UNCOV
179
        return settings;
×
180
    }
181

182
    /** @hidden  @internal */
183
    @HostBinding('attr.aria-expanded')
184
    public get ariaExpanded() {
UNCOV
185
        return !this.collapsed;
×
186
    }
187

188
    /** @hidden  @internal */
189
    @HostBinding('attr.aria-haspopup')
190
    public get hasPopUp() {
UNCOV
191
        return 'listbox';
×
192
    }
193

194
    /** @hidden  @internal */
195
    @HostBinding('attr.aria-owns')
196
    public get ariaOwns() {
UNCOV
197
        return this.target.listId;
×
198
    }
199

200
    /** @hidden  @internal */
201
    @HostBinding('attr.aria-activedescendant')
202
    public get ariaActiveDescendant() {
UNCOV
203
        return !this.target.collapsed && this.target.focusedItem ? this.target.focusedItem.id : null;
×
204
    }
205

206
    /** @hidden  @internal */
207
    @HostBinding('attr.aria-autocomplete')
208
    public get ariaAutocomplete() {
UNCOV
209
        return 'list';
×
210
    }
211

212
    protected _composing: boolean;
213
    protected id: string;
214
    protected get model() {
UNCOV
215
        return this.ngModel || this.formControl;
×
216
    }
217

UNCOV
218
    private _shouldBeOpen = false;
×
UNCOV
219
    private destroy$ = new Subject<void>();
×
220
    private defaultSettings: OverlaySettings;
221

UNCOV
222
    constructor(@Self() @Optional() @Inject(NgModel) protected ngModel: NgModel,
×
UNCOV
223
        @Self() @Optional() @Inject(FormControlName) protected formControl: FormControlName,
×
UNCOV
224
        @Optional() protected group: IgxInputGroupComponent,
×
UNCOV
225
        protected elementRef: ElementRef,
×
UNCOV
226
        protected cdr: ChangeDetectorRef) {
×
UNCOV
227
        super(null);
×
228
    }
229

230
    /** @hidden  @internal */
231
    @HostListener('input')
232
    public onInput() {
UNCOV
233
        this.open();
×
234
    }
235

236
    /** @hidden @internal */
237
    @HostListener('compositionstart')
238
    public onCompositionStart(): void {
239
        if (!this._composing) {
×
240
            this._composing = true;
×
241
        }
242
    }
243

244
    /** @hidden @internal */
245
    @HostListener('compositionend')
246
    public onCompositionEnd(): void {
247
        this._composing = false;
×
248
    }
249

250
    /** @hidden  @internal */
251
    @HostListener('keydown.ArrowDown', ['$event'])
252
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
253
    @HostListener('keydown.ArrowUp', ['$event'])
254
    @HostListener('keydown.Alt.ArrowUp', ['$event'])
255
    public onArrowDown(event: Event) {
UNCOV
256
        event.preventDefault();
×
UNCOV
257
        this.open();
×
258
    }
259

260
    /** @hidden  @internal */
261
    @HostListener('keydown.Tab')
262
    @HostListener('keydown.Shift.Tab')
263
    public onTab() {
UNCOV
264
        this.close();
×
265
    }
266

267
    /** @hidden  @internal */
268
    public override handleKeyDown(event) {
UNCOV
269
        if (!this.collapsed && !this._composing) {
×
UNCOV
270
            switch (event.key.toLowerCase()) {
×
271
                case 'space':
272
                case 'spacebar':
273
                case ' ':
274
                case 'home':
275
                case 'end':
UNCOV
276
                    return;
×
277
                default:
UNCOV
278
                    super.handleKeyDown(event);
×
279
            }
280
        }
281
    }
282

283
    /** @hidden  @internal */
284
    public override onArrowDownKeyDown() {
UNCOV
285
        super.onArrowDownKeyDown();
×
286
    }
287

288
    /** @hidden  @internal */
289
    public override onArrowUpKeyDown() {
UNCOV
290
        super.onArrowUpKeyDown();
×
291
    }
292

293
    /** @hidden  @internal */
294
    public override onEndKeyDown() {
295
        super.onEndKeyDown();
×
296
    }
297

298
    /** @hidden  @internal */
299
    public override onHomeKeyDown() {
300
        super.onHomeKeyDown();
×
301
    }
302

303
    /**
304
     * Closes autocomplete drop down
305
     */
306
    public close() {
UNCOV
307
        this._shouldBeOpen = false;
×
UNCOV
308
        if (this.collapsed) {
×
309
            return;
×
310
        }
UNCOV
311
        this.target.close();
×
312
    }
313

314
    /**
315
     * Opens autocomplete drop down
316
     */
317
    public open() {
UNCOV
318
        this._shouldBeOpen = true;
×
UNCOV
319
        if (this.disabled || !this.collapsed || this.target.children.length === 0) {
×
UNCOV
320
            return;
×
321
        }
322
        // if no drop-down width is set, the drop-down will be as wide as the autocomplete input;
UNCOV
323
        this.target.width = this.target.width || (this.parentElement.clientWidth + 'px');
×
UNCOV
324
        this.target.open(this.settings);
×
UNCOV
325
        this.highlightFirstItem();
×
326
    }
327

328
    /** @hidden @internal */
329
    public ngOnInit() {
UNCOV
330
        const targetElement = this.parentElement;
×
UNCOV
331
        this.defaultSettings = {
×
332
            target: targetElement,
333
            modal: false,
334
            scrollStrategy: new AbsoluteScrollStrategy(),
335
            positionStrategy: new AutoPositionStrategy(),
336
            excludeFromOutsideClick: [targetElement]
337
        };
338
    }
339

340
    /** @hidden */
341
    public ngOnDestroy() {
UNCOV
342
        this.destroy$.next();
×
UNCOV
343
        this.destroy$.complete();
×
344
    }
345

346
    public ngAfterViewInit() {
UNCOV
347
        this.target.children.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
348
            if (this.target.children.length) {
×
UNCOV
349
                if (!this.collapsed) {
×
UNCOV
350
                    this.highlightFirstItem();
×
UNCOV
351
                } else if (this._shouldBeOpen) {
×
UNCOV
352
                    this.open();
×
353
                }
354
            } else {
355
                // _shouldBeOpen flag should remain unchanged since this state change doesn't come from outside of the component
356
                // (like in the case of public API or user interaction).
UNCOV
357
                this.target.close();
×
358
            }
359
        });
UNCOV
360
        this.target.selectionChanging.pipe(takeUntil(this.destroy$)).subscribe(this.select.bind(this));
×
361
    }
362

363
    private get collapsed(): boolean {
UNCOV
364
        return this.target ? this.target.collapsed : true;
×
365
    }
366

367
    private select(value: ISelectionEventArgs) {
UNCOV
368
        if (!value.newSelection) {
×
369
            return;
×
370
        }
UNCOV
371
        value.cancel = true; // Disable selection in the drop down, because in autocomplete we do not save selection.
×
UNCOV
372
        const newValue = value.newSelection.value;
×
UNCOV
373
        const args: AutocompleteSelectionChangingEventArgs = { value: newValue, cancel: false };
×
UNCOV
374
        this.selectionChanging.emit(args);
×
UNCOV
375
        if (args.cancel) {
×
UNCOV
376
            return;
×
377
        }
UNCOV
378
        this.close();
×
379

380
        // Update model after the input is re-focused, in order to have proper valid styling.
381
        // Otherwise when item is selected using mouse (and input is blurred), then valid style will be removed.
UNCOV
382
        if (this.model) {
×
UNCOV
383
            this.model.control.setValue(newValue);
×
384
        } else {
385
            this.nativeElement.value = newValue;
×
386
        }
387
    }
388

389
    private highlightFirstItem() {
UNCOV
390
        if (this.target.focusedItem) {
×
UNCOV
391
            this.target.focusedItem.focused = false;
×
UNCOV
392
            this.target.focusedItem = null;
×
393
        }
UNCOV
394
        this.target.navigateFirst();
×
UNCOV
395
        this.cdr.detectChanges();
×
396
    }
397
}
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