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

IgniteUI / igniteui-angular / 23848019203

01 Apr 2026 12:14PM UTC coverage: 89.259% (+0.001%) from 89.258%
23848019203

Pull #16996

github

web-flow
Merge 6f4f7165a into a68d01e55
Pull Request #16996: fix(grid-pinning): remove hide call in scrollToRow - 21.1.x

14407 of 16973 branches covered (84.88%)

Branch coverage included in aggregate %.

14 of 17 new or added lines in 2 files covered. (82.35%)

11 existing lines in 3 files now uncovered.

29070 of 31736 relevant lines covered (91.6%)

34492.35 hits per line

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

91.47
/projects/igniteui-angular/simple-combo/src/simple-combo/simple-combo.component.ts
1
import { NgTemplateOutlet } from '@angular/common';
2
import { AfterViewInit, Component, DoCheck, EventEmitter, HostListener, Output, ViewChild, inject } from '@angular/core';
3
import { ControlValueAccessor, FormGroupDirective, NG_VALUE_ACCESSOR } from '@angular/forms';
4
import { takeUntil } from 'rxjs/operators';
5

6
import { CancelableEventArgs, IBaseCancelableBrowserEventArgs, IBaseEventArgs, PlatformUtil } from 'igniteui-angular/core';
7
import { IgxButtonDirective } from 'igniteui-angular/directives';
8
import { IgxForOfDirective } from 'igniteui-angular/directives';
9
import { IgxRippleDirective } from 'igniteui-angular/directives';
10
import { IgxTextSelectionDirective } from 'igniteui-angular/directives';
11
import { IgxInputGroupComponent, IgxInputDirective, IgxSuffixDirective } from 'igniteui-angular/input-group';
12
import { IgxIconComponent } from 'igniteui-angular/icon';
13
import { IGX_COMBO_COMPONENT, IgxComboAddItemComponent, IgxComboAPIService, IgxComboBaseDirective, IgxComboDropDownComponent, IgxComboFilteringPipe, IgxComboGroupingPipe, IgxComboItemComponent } from 'igniteui-angular/combo';
14
import { IgxDropDownItemNavigationDirective } from 'igniteui-angular/drop-down';
15

16
/** Emitted when an igx-simple-combo's selection is changing.  */
17
export interface ISimpleComboSelectionChangingEventArgs extends CancelableEventArgs, IBaseEventArgs {
18
    /** An object which represents the value that is currently selected */
19
    oldValue: any;
20
    /** An object which represents the value that will be selected after this event */
21
    newValue: any;
22
    /** An object which represents the item that is currently selected */
23
    oldSelection: any;
24
    /** An object which represents the item that will be selected after this event */
25
    newSelection: any;
26
    /** The text that will be displayed in the combo text box */
27
    displayText: string;
28
}
29

30
/**
31
 * Represents a drop-down list that provides filtering functionality, allowing users to choose a single option from a predefined list.
32
 *
33
 * @igxModule IgxSimpleComboModule
34
 * @igxTheme igx-combo-theme
35
 * @igxKeywords combobox, single combo selection
36
 * @igxGroup Grids & Lists
37
 *
38
 * @remarks
39
 * It provides the ability to filter items as well as perform single selection on the provided data.
40
 * Additionally, it exposes keyboard navigation and custom styling capabilities.
41
 * @example
42
 * ```html
43
 * <igx-simple-combo [itemsMaxHeight]="250" [data]="locationData"
44
 *  [displayKey]="'field'" [valueKey]="'field'"
45
 *  placeholder="Location" searchPlaceholder="Search...">
46
 * </igx-simple-combo>
47
 * ```
48
 */
49
@Component({
50
    selector: 'igx-simple-combo',
51
    templateUrl: 'simple-combo.component.html',
52
    providers: [
53
        IgxComboAPIService,
54
        { provide: IGX_COMBO_COMPONENT, useExisting: IgxSimpleComboComponent },
55
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxSimpleComboComponent, multi: true }
56
    ],
57
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxTextSelectionDirective, IgxSuffixDirective, NgTemplateOutlet, IgxIconComponent, IgxComboDropDownComponent, IgxDropDownItemNavigationDirective, IgxForOfDirective, IgxComboItemComponent, IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, IgxComboFilteringPipe, IgxComboGroupingPipe]
58
})
59
export class IgxSimpleComboComponent extends IgxComboBaseDirective implements ControlValueAccessor, AfterViewInit, DoCheck {
3✔
60
    private platformUtil = inject(PlatformUtil);
137✔
61
    private formGroupDirective = inject(FormGroupDirective, { optional: true });
137✔
62

63
    /** @hidden @internal */
64
    @ViewChild(IgxComboDropDownComponent, { static: true })
65
    public dropdown: IgxComboDropDownComponent;
66

67
    /** @hidden @internal */
68
    @ViewChild(IgxComboAddItemComponent)
69
    public addItem: IgxComboAddItemComponent;
70

71
    /**
72
     * Emitted when item selection is changing, before the selection completes
73
     *
74
     * ```html
75
     * <igx-simple-combo (selectionChanging)='handleSelection()'></igx-simple-combo>
76
     * ```
77
     */
78
    @Output()
79
    public selectionChanging = new EventEmitter<ISimpleComboSelectionChangingEventArgs>();
137✔
80

81
    @ViewChild(IgxTextSelectionDirective, { static: true })
82
    private textSelection: IgxTextSelectionDirective;
83

84
    public override get value(): any {
85
        return this._value[0];
58✔
86
    }
87

88
    /**
89
     * Get current selection state
90
     *
91
     * @returns The selected item, if any
92
     * ```typescript
93
     * let mySelection = this.combo.selection;
94
     * ```
95
     */
96
    public override get selection(): any {
97
        return super.selection[0];
148✔
98
    }
99

100
    /** @hidden @internal */
101
    public composing = false;
137✔
102

103
    private _updateInput = true;
137✔
104

105
    private _collapsing = false;
137✔
106

107
    /** @hidden @internal */
108
    public get filteredData(): any[] | null {
109
        return this._filteredData;
1,720✔
110
    }
111
    /** @hidden @internal */
112
    public set filteredData(val: any[] | null) {
113
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
7,735!
114
        this.checkMatch();
308✔
115
    }
116

117
    /** @hidden @internal */
118
    public override get searchValue(): string {
119
        return this._searchValue;
15,068✔
120
    }
121
    public override set searchValue(val: string) {
122
        this._searchValue = val;
326✔
123
    }
124

125
    private get selectedItem(): any {
126
        return this.selectionService.get(this.id).values().next().value;
144✔
127
    }
128

129
    protected get hasSelectedItem(): boolean {
130
        return !!this.selectionService.get(this.id).size;
1,132✔
131
    }
132

133
    constructor() {
134
        super();
137✔
135
        this.comboAPI.register(this);
137✔
136
    }
137

138
    /** @hidden @internal */
139
    @HostListener('keydown.ArrowDown', ['$event'])
140
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
141
    public onArrowDown(event: Event): void {
142
        if (this.collapsed) {
7✔
143
            event.preventDefault();
3✔
144
            event.stopPropagation();
3✔
145
            this.open();
3✔
146
        } else {
147
            if (this.virtDir.igxForOf.length > 0 && !this.hasSelectedItem) {
4✔
148
                this.dropdown.navigateNext();
3✔
149
                this.dropdownContainer.nativeElement.focus();
3✔
150
            } else if (this.allowCustomValues) {
1✔
151
                this.addItem?.element.nativeElement.focus();
1✔
152
            }
153
        }
154
    }
155

156
    /**
157
     * Select a defined item
158
     *
159
     * @param item the item to be selected
160
     * ```typescript
161
     * this.combo.select("New York");
162
     * ```
163
     */
164
    public select(item: any): void {
165
        if (item !== undefined) {
77✔
166
            const newSelection = this.selectionService.add_items(this.id, item instanceof Array ? item : [item], true);
77✔
167
            this.setSelection(newSelection);
77✔
168
        }
169
    }
170

171
    /**
172
     * Deselect the currently selected item
173
     *
174
     * @param item the items to be deselected
175
     * ```typescript
176
     * this.combo.deselect("New York");
177
     * ```
178
     */
179
    public deselect(): void {
180
        this.clearSelection();
4✔
181
    }
182

183
    /** @hidden @internal */
184
    public writeValue(value: any): void {
185
        const oldSelection = super.selection;
104✔
186
        this.selectionService.select_items(this.id, this.isValid(value) ? [value] : [], true);
104✔
187
        this.cdr.markForCheck();
104✔
188
        this._displayValue = this.createDisplayText(super.selection, oldSelection);
104✔
189
        this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
104✔
190
        this.searchValue = this.filterValue = this._displayValue?.toString() || '';
104✔
191
    }
192

193
    /** @hidden @internal */
194
    public override ngAfterViewInit(): void {
195
        this.virtDir.contentSizeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
122✔
196
            if (super.selection.length > 0) {
1✔
197
                const index = this.virtDir.igxForOf.findIndex(e => {
1✔
198
                    let current = e ? e[this.valueKey] : undefined;
10!
199
                    if (this.valueKey === null || this.valueKey === undefined) {
10!
200
                        current = e;
×
201
                    }
202
                    return current === super.selection[0];
10✔
203
                });
204
                if (!this.isRemote) {
1!
205
                    // navigate to item only if we have local data
206
                    // as with remote data this will fiddle with igxFor's scroll handler
207
                    // and will trigger another chunk load which will break the visualization
208
                    this.dropdown.navigateItem(index);
×
209
                }
210
            }
211
        });
212
        this.dropdown.opening.pipe(takeUntil(this.destroy$)).subscribe((args) => {
122✔
213
            if (args.cancel) {
74!
214
                return;
×
215
            }
216
            this._collapsing = false;
74✔
217
            const filtered = this.filteredData.find(this.findAllMatches);
74✔
218
            if (filtered === undefined || filtered === null) {
74✔
219
                this.filterValue = this.searchValue = this.comboInput.value;
45✔
220
                return;
45✔
221
            }
222
        });
223
        this.dropdown.opened.pipe(takeUntil(this.destroy$)).subscribe(() => {
122✔
224
            if (this.composing) {
28✔
225
                this.comboInput.focus();
4✔
226
            }
227
        });
228
        this.dropdown.closing.pipe(takeUntil(this.destroy$)).subscribe((args) => {
122✔
229
            if (args.cancel) {
39!
230
                return;
×
231
            }
232
            if (this.getEditElement() && !args.event) {
39✔
233
                this._collapsing = true;
36✔
234
                // Only focus back when programmatically closing (no user event)
235
                // to avoid focus loops when user clicks on another combo
236
                this.comboInput.focus();
36✔
237
            } else {
238
                this.clearOnBlur();
3✔
239
                this._onTouchedCallback();
3✔
240
            }
241
        });
242

243
        // in reactive form the control is not present initially
244
        // and sets the selection to an invalid value in writeValue method
245
        if (!this.isValid(this.selectedItem)) {
122✔
246
            this.selectionService.clear(this.id);
101✔
247
            this._displayValue = '';
101✔
248
        }
249

250
        super.ngAfterViewInit();
122✔
251
    }
252

253
    /** @hidden @internal */
254
    public ngDoCheck(): void {
255
        if (this.data?.length && super.selection.length && !this._displayValue) {
482✔
256
            this._displayValue = this.createDisplayText(super.selection, []);
44✔
257
            this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
44✔
258
        }
259
    }
260

261
    /** @hidden @internal */
262
    public override handleInputChange(event?: any): void {
263
        if (this.collapsed && this.comboInput.focused) {
80✔
264
            this.open();
11✔
265
        }
266
        if (event !== undefined) {
80✔
267
            this.filterValue = this.searchValue = typeof event === 'string' ? event : event.target.value;
78✔
268
        }
269
        if (!this.comboInput.value.trim() && super.selection.length) {
80✔
270
            // handle clearing of input by space
271
            this.clearSelection();
1✔
272
            this._onChangeCallback(null);
1✔
273
            this.filterValue = '';
1✔
274
        }
275
        if (super.selection.length) {
80✔
276
            const args: ISimpleComboSelectionChangingEventArgs = {
9✔
277
                newValue: undefined,
278
                oldValue: this.selectedItem,
279
                newSelection: undefined,
280
                oldSelection: this.selection,
281
                displayText: typeof event === 'string' ? event : event?.target?.value,
9✔
282
                owner: this,
283
                cancel: false
284
            };
285
            this.selectionChanging.emit(args);
9✔
286
            if (!args.cancel) {
9✔
287
                this.selectionService.select_items(this.id, [], true);
5✔
288
            }
289
        }
290
        // when filtering the focused item should be the first item or the currently selected item
291
        if (!this.dropdown.focusedItem || this.dropdown.focusedItem.id !== this.dropdown.items[0].id) {
80✔
292
            this.dropdown.navigateFirst();
29✔
293
        }
294
        super.handleInputChange(event);
80✔
295
        this.composing = true;
80✔
296
    }
297

298
    /** @hidden @internal */
299
    public handleInputClick(): void {
300
        if (this.collapsed) {
4✔
301
            this.open();
2✔
302
            this.comboInput.focus();
2✔
303
        }
304
    }
305

306
    /** @hidden @internal */
307
    public override handleKeyDown(event: KeyboardEvent): void {
308
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
102✔
309
            const filtered = this.filteredData.find(this.findAllMatches);
9✔
310
            if (filtered === null || filtered === undefined) {
9!
311
                return;
×
312
            }
313
            if (!this.dropdown.collapsed) {
9✔
314
                const focusedItem = this.dropdown.focusedItem;
8✔
315
                if (focusedItem && !focusedItem.isHeader) {
8✔
316
                    this.select(focusedItem.itemID);
7✔
317
                    event.preventDefault();
7✔
318
                    event.stopPropagation();
7✔
319
                    this.close();
7✔
320
                } else {
321
                    event.preventDefault();
1✔
322
                    event.stopPropagation();
1✔
323
                    this.comboInput.focus();
1✔
324
                }
325
            }
326
            // manually trigger text selection as it will not be triggered during editing
327
            this.textSelection.trigger();
9✔
328
            return;
9✔
329
        }
330
        if (event.key === this.platformUtil.KEYMAP.BACKSPACE
93✔
331
            || event.key === this.platformUtil.KEYMAP.DELETE) {
332
            this._updateInput = false;
2✔
333
            this.clearSelection(true);
2✔
334
        }
335
        if (!this.collapsed && event.key === this.platformUtil.KEYMAP.TAB) {
93✔
336
            const filtered = this.filteredData.find(this.findAllMatches);
14✔
337
            if (filtered === null || filtered === undefined) {
14✔
338
                this.clearOnBlur();
6✔
339
                this.close();
6✔
340
                return;
6✔
341
            }
342
            const focusedItem = this.dropdown.focusedItem;
8✔
343
            if (focusedItem && !focusedItem.isHeader) {
8✔
344
                this.select(focusedItem.itemID);
7✔
345
                this.close();
7✔
346
                this.textSelection.trigger();
7✔
347
            } else {
348
                this.clearOnBlur();
1✔
349
                this.close();
1✔
350
            }
351
        }
352
        if (event.key === this.platformUtil.KEYMAP.ESCAPE) {
87✔
353
            event.stopPropagation();
3✔
354
            if (this.collapsed) {
3✔
355
                const oldSelection = this.selection;
2✔
356
                this.clearSelection(true);
2✔
357
                if (this.selection !== oldSelection) {
2✔
358
                    this.comboInput.value = this.filterValue = this.searchValue = '';
2✔
359
                }
360
                this.dropdown.focusedItem = null;
2✔
361
                this.comboInput.focus();
2✔
362
            } else {
363
                this.close();
1✔
364
            }
365
        }
366
        this.composing = false;
87✔
367
        super.handleKeyDown(event);
87✔
368
    }
369

370
    /** @hidden @internal */
371
    public handleKeyUp(event: KeyboardEvent): void {
372
        if (event.key === this.platformUtil.KEYMAP.ARROW_DOWN) {
67✔
373
            this.dropdown.focusedItem = this.hasSelectedItem && this.filteredData.length > 0
3!
UNCOV
374
                ? this.dropdown.items.find(i => i.itemID === this.selectedItem)
×
375
                : this.dropdown.items[0];
376
            this.dropdownContainer.nativeElement.focus();
3✔
377
        }
378
    }
379

380
    /** @hidden @internal */
381
    public handleItemKeyDown(event: KeyboardEvent): void {
382
        if (event.key === this.platformUtil.KEYMAP.ARROW_UP && event.altKey) {
8!
383
            this.close();
×
384
            this.comboInput.focus();
×
UNCOV
385
            return;
×
386
        }
387
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
8!
UNCOV
388
            this.comboInput.focus();
×
389
        }
390
    }
391

392
    /** @hidden @internal */
393
    public handleItemClick(): void {
394
        this.close();
27✔
395
        this.comboInput.focus();
27✔
396
    }
397

398
    /** @hidden @internal */
399
    public override onBlur(): void {
400
        // when clicking the toggle button to close the combo and immediately clicking outside of it
401
        // the collapsed state is not modified as the dropdown is still not closed
402
        if (this.collapsed || this._collapsing) {
15✔
403
            this.clearOnBlur();
10✔
404
        }
405
        super.onBlur();
15✔
406
    }
407

408
    /** @hidden @internal */
409
    public getEditElement(): HTMLElement {
410
        return this.comboInput.nativeElement;
39✔
411
    }
412

413
    /** @hidden @internal */
414
    public handleClear(event: Event): void {
415
        if (this.disabled) {
7✔
416
            return;
1✔
417
        }
418

419
        const oldSelection = this.selection;
6✔
420
        this.clearSelection(true);
6✔
421

422
        if (!this.collapsed) {
6✔
423
            this.focusSearchInput(true);
2✔
424
        }
425
        event.stopPropagation();
6✔
426

427
        if (this.selection !== oldSelection) {
6✔
428
            this.comboInput.value = this.filterValue = this.searchValue = '';
5✔
429
        }
430

431
        this.dropdown.focusedItem = null;
6✔
432
        this.composing = false;
6✔
433
        this.comboInput.focus();
6✔
434
    }
435

436
    /** @hidden @internal */
437
    public handleOpened(): void {
438
        this.triggerCheck();
28✔
439
        if (!this.comboInput.focused) {
28✔
440
            this.dropdownContainer.nativeElement.focus();
15✔
441
        }
442
        this.opened.emit({ owner: this });
28✔
443
    }
444

445
    /** @hidden @internal */
446
    public override handleClosing(e: IBaseCancelableBrowserEventArgs): void {
447
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
41✔
448
        this.closing.emit(args);
41✔
449
        e.cancel = args.cancel;
41✔
450
        if (e.cancel) {
41✔
451
            return;
1✔
452
        }
453

454
        this.composing = false;
40✔
455
        // explicitly update selection so that we don't have to force CD
456
        const isTab = (e.event as KeyboardEvent)?.key === this.platformUtil.KEYMAP.TAB;
40✔
457
        this.textSelection.selected = !isTab;
40✔
458
    }
459

460
    /** @hidden @internal */
461
    public focusSearchInput(opening?: boolean): void {
462
        if (opening) {
8✔
463
            this.dropdownContainer.nativeElement.focus();
2✔
464
        } else {
465
            this.comboInput.nativeElement.focus();
6✔
466
        }
467
    }
468

469
    /** @hidden @internal */
470
    public override onClick(event: Event): void {
471
        super.onClick(event);
8✔
472
        if (this.comboInput.value.length === 0) {
8✔
473
            this.virtDir.scrollTo(0);
6✔
474
        }
475
    }
476

477
    protected findAllMatches = (element: any): boolean => {
137✔
478
        const value = this.displayKey ? element[this.displayKey] : element;
1,924✔
479
        if (value === null || value === undefined || value === '') {
1,924✔
480
            // we can accept null, undefined and empty strings as empty display values
481
            return true;
13✔
482
        }
483
        const searchValue = this.searchValue || this.comboInput.value;
1,911✔
484
        return !!searchValue && value.toString().toLowerCase().includes(searchValue.toLowerCase());
1,911✔
485
    };
486

487
    protected setSelection(newSelection: any): void {
488
        const newValueAsArray = newSelection ? Array.from(newSelection) as IgxComboItemComponent[] : [];
94✔
489
        const oldValueAsArray = Array.from(this.selectionService.get(this.id) || []);
94!
490
        const newItems = this.convertKeysToItems(newValueAsArray);
94✔
491
        const oldItems = this.convertKeysToItems(oldValueAsArray);
94✔
492
        const displayText = this.createDisplayText(this.convertKeysToItems(newValueAsArray), oldValueAsArray);
94✔
493
        const args: ISimpleComboSelectionChangingEventArgs = {
94✔
494
            newValue: newValueAsArray[0],
495
            oldValue: oldValueAsArray[0],
496
            newSelection: newItems[0],
497
            oldSelection: oldItems[0],
498
            displayText,
499
            owner: this,
500
            cancel: false
501
        };
502
        if (args.newSelection !== args.oldSelection) {
94✔
503
            this.selectionChanging.emit(args);
89✔
504
        }
505
        // TODO: refactor below code as it sets the selection and the display text
506
        if (!args.cancel) {
94✔
507
            let argsSelection = this.isValid(args.newValue)
92✔
508
                ? args.newValue
509
                : [];
510
            argsSelection = Array.isArray(argsSelection) ? argsSelection : [argsSelection];
92✔
511
            this.selectionService.select_items(this.id, argsSelection, true);
92✔
512
            this._value = argsSelection;
92✔
513
            if (this._updateInput) {
92✔
514
                this.comboInput.value = this._displayValue = this.searchValue = displayText !== args.displayText
90!
515
                    ? args.displayText
516
                    : this.createDisplayText(super.selection, [args.oldValue]);
517
            }
518
            this._onChangeCallback(args.newValue);
92✔
519
            this._updateInput = true;
92✔
520
        } else if (this.isRemote) {
2✔
521
            this.registerRemoteEntries(newValueAsArray, false);
1✔
522
        } else {
523
            args.displayText = this.createDisplayText(oldItems, []);
1✔
524

525
            const oldSelectionArray = args.oldSelection ? [args.oldSelection] : [];
1!
526
            this.comboInput.value = this._displayValue = this.searchValue = this.createDisplayText(oldSelectionArray, []);
1✔
527

528
            if (this.isRemote) {
1!
UNCOV
529
                this.registerRemoteEntries(newValueAsArray, false);
×
530
            }
531
        }
532
    }
533

534
    protected createDisplayText(newSelection: any[], oldSelection: any[]): string {
535
        if (this.isRemote) {
334✔
536
            const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
31!
537
            return this.getRemoteSelection(selection, oldSelection);
31✔
538
        }
539

540
        if (this.displayKey !== null
303✔
541
            && this.displayKey !== undefined
542
            && newSelection.length > 0) {
543
            return newSelection.filter(e => e).map(e => e[this.displayKey])[0]?.toString() || '';
141✔
544
        }
545

546
        return newSelection[0]?.toString() || '';
164✔
547
    }
548

549
    protected override getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
550
        if (!newSelection.length) {
31✔
551
            this.registerRemoteEntries(oldSelection, false);
6✔
552
            return '';
6✔
553
        }
554

555
        this.registerRemoteEntries(oldSelection, false);
25✔
556
        this.registerRemoteEntries(newSelection);
25✔
557
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
25✔
558
    }
559

560
    /** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
561
    protected override registerRemoteEntries(ids: any[], add = true) {
25✔
562
        const selection = this.getValueDisplayPairs(ids)[0];
57✔
563

564
        if (add && selection) {
57✔
565
            this._remoteSelection[selection[this.valueKey]] = selection[this.displayKey].toString();
23✔
566
        } else {
567
            this._remoteSelection = {};
34✔
568
        }
569
    }
570

571
    private clearSelection(ignoreFilter?: boolean): void {
572
        let newSelection = this.selectionService.get_empty();
31✔
573
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
31✔
574
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
1✔
575
        }
576
        if (this.selectionService.get(this.id).size > 0 || this.comboInput.value.trim()) {
31✔
577
            this.setSelection(newSelection);
17✔
578
        }
579
    }
580

581
    private clearOnBlur(): void {
582
        if (this.isRemote) {
20✔
583
            const searchValue = this.searchValue || this.comboInput.value;
1!
584
            const remoteValue = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
1!
585
            if (searchValue !== remoteValue) {
1!
UNCOV
586
                this.clear();
×
587
            }
588
            return;
1✔
589
        }
590

591
        const filtered = this.filteredData.find(this.findMatch);
19✔
592
        // selecting null in primitive data returns undefined as the search text is '', but the item is null
593
        if (filtered === undefined && this.selectedItem !== null || !super.selection.length) {
19✔
594
            this.clear();
16✔
595
        }
596
    }
597

598
    private getElementVal(element: any): string {
599
        const elementVal = this.displayKey ? element[this.displayKey] : element;
×
UNCOV
600
        return String(elementVal);
×
601
    }
602

603
    private clear(): void {
604
        this.clearSelection(true);
16✔
605
        const oldSelection = this.selection;
16✔
606
        if (this.selection !== oldSelection) {
16!
UNCOV
607
            this.comboInput.value = this._displayValue = this.searchValue = '';
×
608
        }
609
    }
610

611
    private isValid(value: any): boolean {
612
        if (this.formGroupDirective && value === null) {
318✔
613
            return false;
11✔
614
        }
615

616
        if (this.required) {
307✔
617
            return value !== null && value !== '' && value !== undefined
48✔
618
        }
619

620
        return value !== undefined;
259✔
621
    }
622
}
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