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

IgniteUI / igniteui-angular / 23848129113

01 Apr 2026 12:17PM UTC coverage: 90.163% (+0.7%) from 89.452%
23848129113

Pull #16784

github

web-flow
Merge b19ccc7d2 into f00a27f08
Pull Request #16784: fix(grid-pinning): remove hide call in scrollToRow - master

14823 of 17264 branches covered (85.86%)

Branch coverage included in aggregate %.

15 of 17 new or added lines in 2 files covered. (88.24%)

264 existing lines in 15 files now uncovered.

29886 of 32323 relevant lines covered (92.46%)

34071.84 hits per line

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

92.12
/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 the Combo's selection has changed. */
17
export interface ISimpleComboSelectionChangedEventArgs extends IBaseEventArgs {
18
    /** The old selection value */
19
    oldValue: any;
20
    /** The new selection value */
21
    newValue: any;
22
    /** The old selection item */
23
    oldSelection: any;
24
    /** The new selection item */
25
    newSelection: any;
26
    /** The display text of the combo text box */
27
    displayText: string;
28
}
29

30
/** Emitted when the Combo's selection is changing. */
31
export interface ISimpleComboSelectionChangingEventArgs extends ISimpleComboSelectionChangedEventArgs, CancelableEventArgs {}
32

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

66
    /** @hidden @internal */
67
    @ViewChild(IgxComboDropDownComponent, { static: true })
68
    public dropdown: IgxComboDropDownComponent;
69

70
    /** @hidden @internal */
71
    @ViewChild(IgxComboAddItemComponent)
72
    public addItem: IgxComboAddItemComponent;
73

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

84
    /**
85
     * Emitted when item selection is changed, after the selection completes
86
     *
87
     * ```html
88
     * <igx-simple-combo (selectionChanged)='handleSelection()'></igx-simple-combo>
89
     * ```
90
     */
91
    @Output()
92
    public selectionChanged = new EventEmitter<ISimpleComboSelectionChangedEventArgs>();
143✔
93

94
    @ViewChild(IgxTextSelectionDirective, { static: true })
95
    private textSelection: IgxTextSelectionDirective;
96

97
    public override get value(): any {
98
        return this._value[0];
247✔
99
    }
100

101
    /**
102
     * Get current selection state
103
     *
104
     * @returns The selected item, if any
105
     * ```typescript
106
     * let mySelection = this.combo.selection;
107
     * ```
108
     */
109
    public override get selection(): any {
110
        return super.selection[0];
246✔
111
    }
112

113
    /** @hidden @internal */
114
    public composing = false;
143✔
115

116
    private _updateInput = true;
143✔
117

118
    private _collapsing = false;
143✔
119

120
    /** @hidden @internal */
121
    public get filteredData(): any[] | null {
122
        return this._filteredData;
1,741✔
123
    }
124
    /** @hidden @internal */
125
    public set filteredData(val: any[] | null) {
126
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
8,041!
127
        this.checkMatch();
314✔
128
    }
129

130
    /** @hidden @internal */
131
    public override get searchValue(): string {
132
        return this._searchValue;
15,389✔
133
    }
134
    public override set searchValue(val: string) {
135
        this._searchValue = val;
331✔
136
    }
137

138
    private get selectedItem(): any {
139
        return this.selectionService.get(this.id).values().next().value;
152✔
140
    }
141

142
    protected get hasSelectedItem(): boolean {
143
        return !!this.selectionService.get(this.id).size;
1,147✔
144
    }
145

146
    constructor() {
147
        super();
143✔
148
        this.comboAPI.register(this);
143✔
149
    }
150

151
    /** @hidden @internal */
152
    @HostListener('keydown.ArrowDown', ['$event'])
153
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
154
    public onArrowDown(event: Event): void {
155
        if (this.collapsed) {
7✔
156
            event.preventDefault();
3✔
157
            event.stopPropagation();
3✔
158
            this.open();
3✔
159
        } else {
160
            if (this.virtDir.igxForOf.length > 0 && !this.hasSelectedItem) {
4✔
161
                this.dropdown.navigateNext();
3✔
162
                this.dropdownContainer.nativeElement.focus();
3✔
163
            } else if (this.allowCustomValues) {
1✔
164
                this.addItem?.element.nativeElement.focus();
1✔
165
            }
166
        }
167
    }
168

169
    /**
170
     * Select a defined item
171
     *
172
     * @param item the item to be selected
173
     * ```typescript
174
     * this.combo.select("New York");
175
     * ```
176
     */
177
    public select(item: any): void {
178
        if (item !== undefined) {
82✔
179
            const newSelection = this.selectionService.add_items(this.id, item instanceof Array ? item : [item], true);
82✔
180
            this.setSelection(newSelection);
82✔
181
        }
182
    }
183

184
    /**
185
     * Deselect the currently selected item
186
     *
187
     * @param item the items to be deselected
188
     * ```typescript
189
     * this.combo.deselect("New York");
190
     * ```
191
     */
192
    public deselect(): void {
193
        this.clearSelection();
4✔
194
    }
195

196
    /** @hidden @internal */
197
    public writeValue(value: any): void {
198
        const oldSelection = super.selection;
104✔
199
        this.selectionService.select_items(this.id, this.isValid(value) ? [value] : [], true);
104✔
200
        this.cdr.markForCheck();
104✔
201
        this._displayValue = this.createDisplayText(super.selection, oldSelection);
104✔
202
        this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
104✔
203
        this.searchValue = this.filterValue = this._displayValue?.toString() || '';
104✔
204
    }
205

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

256
        // in reactive form the control is not present initially
257
        // and sets the selection to an invalid value in writeValue method
258
        if (!this.isValid(this.selectedItem)) {
125✔
259
            this.selectionService.clear(this.id);
104✔
260
            this._displayValue = '';
104✔
261
        }
262

263
        super.ngAfterViewInit();
125✔
264
    }
265

266
    /** @hidden @internal */
267
    public ngDoCheck(): void {
268
        if (this.data?.length && super.selection.length && !this._displayValue) {
488✔
269
            this._displayValue = this.createDisplayText(super.selection, []);
44✔
270
            this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
44✔
271
        }
272
    }
273

274
    /** @hidden @internal */
275
    public override handleInputChange(event?: any): void {
276
        if (this.collapsed && this.comboInput.focused) {
80✔
277
            this.open();
11✔
278
        }
279
        if (event !== undefined) {
80✔
280
            this.filterValue = this.searchValue = typeof event === 'string' ? event : event.target.value;
78✔
281
        }
282
        if (!this.comboInput.value.trim() && super.selection.length) {
80✔
283
            // handle clearing of input by space
284
            this.clearSelection();
1✔
285
            this._onChangeCallback(null);
1✔
286
            this.filterValue = '';
1✔
287
        }
288
        if (super.selection.length) {
80✔
289
            const args: ISimpleComboSelectionChangingEventArgs = {
9✔
290
                newValue: undefined,
291
                oldValue: this.selectedItem,
292
                newSelection: undefined,
293
                oldSelection: this.selection,
294
                displayText: typeof event === 'string' ? event : event?.target?.value,
9✔
295
                owner: this,
296
                cancel: false
297
            };
298
            this.selectionChanging.emit(args);
9✔
299
            if (!args.cancel) {
9✔
300
                this.selectionService.select_items(this.id, [], true);
5✔
301
                const changedArgs: ISimpleComboSelectionChangedEventArgs = {
5✔
302
                    newValue: undefined,
303
                    oldValue: this.selectedItem,
304
                    newSelection: undefined,
305
                    oldSelection: this.selection,
306
                    displayText: typeof event === 'string' ? event : event?.target?.value,
5✔
307
                    owner: this
308
                };
309
                this.selectionChanged.emit(changedArgs);
5✔
310
            }
311
        }
312
        // when filtering the focused item should be the first item or the currently selected item
313
        if (!this.dropdown.focusedItem || this.dropdown.focusedItem.id !== this.dropdown.items[0].id) {
80✔
314
            this.dropdown.navigateFirst();
29✔
315
        }
316
        super.handleInputChange(event);
80✔
317
        this.composing = true;
80✔
318
    }
319

320
    /** @hidden @internal */
321
    public handleInputClick(): void {
322
        if (this.collapsed) {
4✔
323
            this.open();
2✔
324
            this.comboInput.focus();
2✔
325
        }
326
    }
327

328
    /** @hidden @internal */
329
    public override handleKeyDown(event: KeyboardEvent): void {
330
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
102✔
331
            const filtered = this.filteredData.find(this.findAllMatches);
9✔
332
            if (filtered === null || filtered === undefined) {
9!
333
                return;
×
334
            }
335
            if (!this.dropdown.collapsed) {
9✔
336
                const focusedItem = this.dropdown.focusedItem;
8✔
337
                if (focusedItem && !focusedItem.isHeader) {
8✔
338
                    this.select(focusedItem.itemID);
7✔
339
                    event.preventDefault();
7✔
340
                    event.stopPropagation();
7✔
341
                    this.close();
7✔
342
                } else {
343
                    event.preventDefault();
1✔
344
                    event.stopPropagation();
1✔
345
                    this.comboInput.focus();
1✔
346
                }
347
            }
348
            // manually trigger text selection as it will not be triggered during editing
349
            this.textSelection.trigger();
9✔
350
            return;
9✔
351
        }
352
        if (event.key === this.platformUtil.KEYMAP.BACKSPACE
93✔
353
            || event.key === this.platformUtil.KEYMAP.DELETE) {
354
            this._updateInput = false;
2✔
355
            this.clearSelection(true);
2✔
356
        }
357
        if (!this.collapsed && event.key === this.platformUtil.KEYMAP.TAB) {
93✔
358
            const filtered = this.filteredData.find(this.findAllMatches);
14✔
359
            if (filtered === null || filtered === undefined) {
14✔
360
                this.clearOnBlur();
6✔
361
                this.close();
6✔
362
                return;
6✔
363
            }
364
            const focusedItem = this.dropdown.focusedItem;
8✔
365
            if (focusedItem && !focusedItem.isHeader) {
8✔
366
                this.select(focusedItem.itemID);
7✔
367
                this.close();
7✔
368
                this.textSelection.trigger();
7✔
369
            } else {
370
                this.clearOnBlur();
1✔
371
                this.close();
1✔
372
            }
373
        }
374
        if (event.key === this.platformUtil.KEYMAP.ESCAPE) {
87✔
375
            event.stopPropagation();
3✔
376
            if (this.collapsed) {
3✔
377
                const oldSelection = this.selection;
2✔
378
                this.clearSelection(true);
2✔
379
                if (this.selection !== oldSelection) {
2✔
380
                    this.comboInput.value = this.filterValue = this.searchValue = '';
2✔
381
                }
382
                this.dropdown.focusedItem = null;
2✔
383
                this.comboInput.focus();
2✔
384
            } else {
385
                this.close();
1✔
386
            }
387
        }
388
        this.composing = false;
87✔
389
        super.handleKeyDown(event);
87✔
390
    }
391

392
    /** @hidden @internal */
393
    public handleKeyUp(event: KeyboardEvent): void {
394
        if (event.key === this.platformUtil.KEYMAP.ARROW_DOWN) {
67✔
395
            this.dropdown.focusedItem = this.hasSelectedItem && this.filteredData.length > 0
3!
UNCOV
396
                ? this.dropdown.items.find(i => i.itemID === this.selectedItem)
×
397
                : this.dropdown.items[0];
398
            this.dropdownContainer.nativeElement.focus();
3✔
399
        }
400
    }
401

402
    /** @hidden @internal */
403
    public handleItemKeyDown(event: KeyboardEvent): void {
404
        if (event.key === this.platformUtil.KEYMAP.ARROW_UP && event.altKey) {
8!
405
            this.close();
×
406
            this.comboInput.focus();
×
UNCOV
407
            return;
×
408
        }
409
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
8!
UNCOV
410
            this.comboInput.focus();
×
411
        }
412
    }
413

414
    /** @hidden @internal */
415
    public handleItemClick(): void {
416
        this.close();
27✔
417
        this.comboInput.focus();
27✔
418
    }
419

420
    /** @hidden @internal */
421
    public override onBlur(): void {
422
        // when clicking the toggle button to close the combo and immediately clicking outside of it
423
        // the collapsed state is not modified as the dropdown is still not closed
424
        if (this.collapsed || this._collapsing) {
15✔
425
            this.clearOnBlur();
10✔
426
        }
427
        super.onBlur();
15✔
428
    }
429

430
    /** @hidden @internal */
431
    public getEditElement(): HTMLElement {
432
        return this.comboInput.nativeElement;
39✔
433
    }
434

435
    /** @hidden @internal */
436
    public handleClear(event: Event): void {
437
        if (this.disabled) {
7✔
438
            return;
1✔
439
        }
440

441
        const oldSelection = this.selection;
6✔
442
        this.clearSelection(true);
6✔
443

444
        if (!this.collapsed) {
6✔
445
            this.focusSearchInput(true);
2✔
446
        }
447
        event.stopPropagation();
6✔
448

449
        if (this.selection !== oldSelection) {
6✔
450
            this.comboInput.value = this.filterValue = this.searchValue = '';
5✔
451
        }
452

453
        this.dropdown.focusedItem = null;
6✔
454
        this.composing = false;
6✔
455
        this.comboInput.focus();
6✔
456
    }
457

458
    /** @hidden @internal */
459
    public handleOpened(): void {
460
        this.triggerCheck();
28✔
461
        if (!this.comboInput.focused) {
28✔
462
            this.dropdownContainer.nativeElement.focus();
15✔
463
        }
464
        this.opened.emit({ owner: this });
28✔
465
    }
466

467
    /** @hidden @internal */
468
    public override handleClosing(e: IBaseCancelableBrowserEventArgs): void {
469
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
41✔
470
        this.closing.emit(args);
41✔
471
        e.cancel = args.cancel;
41✔
472
        if (e.cancel) {
41✔
473
            return;
1✔
474
        }
475

476
        this.composing = false;
40✔
477
        // explicitly update selection so that we don't have to force CD
478
        const isTab = (e.event as KeyboardEvent)?.key === this.platformUtil.KEYMAP.TAB;
40✔
479
        this.textSelection.selected = !isTab;
40✔
480
    }
481

482
    /** @hidden @internal */
483
    public focusSearchInput(opening?: boolean): void {
484
        if (opening) {
8✔
485
            this.dropdownContainer.nativeElement.focus();
2✔
486
        } else {
487
            this.comboInput.nativeElement.focus();
6✔
488
        }
489
    }
490

491
    /** @hidden @internal */
492
    public override onClick(event: Event): void {
493
        super.onClick(event);
8✔
494
        if (this.comboInput.value.length === 0) {
8✔
495
            this.virtDir.scrollTo(0);
6✔
496
        }
497
    }
498

499
    protected findAllMatches = (element: any): boolean => {
143✔
500
        const value = this.displayKey ? element[this.displayKey] : element;
1,924✔
501
        if (value === null || value === undefined || value === '') {
1,924✔
502
            // we can accept null, undefined and empty strings as empty display values
503
            return true;
13✔
504
        }
505
        const searchValue = this.searchValue || this.comboInput.value;
1,911✔
506
        return !!searchValue && value.toString().toLowerCase().includes(searchValue.toLowerCase());
1,911✔
507
    };
508

509
    protected setSelection(newSelection: any): void {
510
        const newValueAsArray = newSelection ? Array.from(newSelection) as IgxComboItemComponent[] : [];
99✔
511
        const oldValueAsArray = Array.from(this.selectionService.get(this.id) || []);
99!
512
        const newItems = this.convertKeysToItems(newValueAsArray);
99✔
513
        const oldItems = this.convertKeysToItems(oldValueAsArray);
99✔
514
        const displayText = this.createDisplayText(this.convertKeysToItems(newValueAsArray), oldValueAsArray);
99✔
515
        const args: ISimpleComboSelectionChangingEventArgs = {
99✔
516
            newValue: newValueAsArray[0],
517
            oldValue: oldValueAsArray[0],
518
            newSelection: newItems[0],
519
            oldSelection: oldItems[0],
520
            displayText,
521
            owner: this,
522
            cancel: false
523
        };
524
        const shouldEmitSelectionEvents = args.newSelection !== args.oldSelection;
99✔
525
        if (shouldEmitSelectionEvents) {
99✔
526
            this.selectionChanging.emit(args);
94✔
527
        }
528
        // TODO: refactor below code as it sets the selection and the display text
529
        if (!args.cancel) {
99✔
530
            let argsSelection = this.isValid(args.newValue)
96✔
531
                ? args.newValue
532
                : [];
533
            argsSelection = Array.isArray(argsSelection) ? argsSelection : [argsSelection];
96✔
534
            this.selectionService.select_items(this.id, argsSelection, true);
96✔
535
            this._value = argsSelection;
96✔
536
            if (this._updateInput) {
96✔
537
                this.comboInput.value = this._displayValue = this.searchValue = displayText !== args.displayText
94✔
538
                    ? args.displayText
539
                    : this.createDisplayText(super.selection, [args.oldValue]);
540
            }
541
            this._onChangeCallback(this.value);
96✔
542
            if (shouldEmitSelectionEvents) {
96✔
543
                const changedArgs: ISimpleComboSelectionChangedEventArgs = {
91✔
544
                    newValue: this.value,
545
                    oldValue: oldValueAsArray[0],
546
                    newSelection: this.selection,
547
                    oldSelection: oldItems[0],
548
                    displayText: this._displayValue,
549
                    owner: this
550
                };
551
                this.selectionChanged.emit(changedArgs);
91✔
552
            }
553
            this._updateInput = true;
96✔
554
        } else if (this.isRemote) {
3✔
555
            this.registerRemoteEntries(newValueAsArray, false);
1✔
556
        } else {
557
            args.displayText = this.createDisplayText(oldItems, []);
2✔
558

559
            const oldSelectionArray = args.oldSelection ? [args.oldSelection] : [];
2✔
560
            this.comboInput.value = this._displayValue = this.searchValue = this.createDisplayText(oldSelectionArray, []);
2✔
561

562
            if (this.isRemote) {
2!
UNCOV
563
                this.registerRemoteEntries(newValueAsArray, false);
×
564
            }
565
        }
566
    }
567

568
    protected createDisplayText(newSelection: any[], oldSelection: any[]): string {
569
        if (this.isRemote) {
344✔
570
            const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
31!
571
            return this.getRemoteSelection(selection, oldSelection);
31✔
572
        }
573

574
        if (this.displayKey !== null
313✔
575
            && this.displayKey !== undefined
576
            && newSelection.length > 0) {
577
            return newSelection.filter(e => e).map(e => e[this.displayKey])[0]?.toString() || '';
145✔
578
        }
579

580
        return newSelection[0]?.toString() || '';
170✔
581
    }
582

583
    protected override getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
584
        if (!newSelection.length) {
31✔
585
            this.registerRemoteEntries(oldSelection, false);
6✔
586
            return '';
6✔
587
        }
588

589
        this.registerRemoteEntries(oldSelection, false);
25✔
590
        this.registerRemoteEntries(newSelection);
25✔
591
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
25✔
592
    }
593

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

598
        if (add && selection) {
57✔
599
            this._remoteSelection[selection[this.valueKey]] = selection[this.displayKey].toString();
23✔
600
        } else {
601
            this._remoteSelection = {};
34✔
602
        }
603
    }
604

605
    private clearSelection(ignoreFilter?: boolean): void {
606
        let newSelection = this.selectionService.get_empty();
31✔
607
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
31✔
608
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
1✔
609
        }
610
        if (this.selectionService.get(this.id).size > 0 || this.comboInput.value.trim()) {
31✔
611
            this.setSelection(newSelection);
17✔
612
        }
613
    }
614

615
    private clearOnBlur(): void {
616
        if (this.isRemote) {
20✔
617
            const searchValue = this.searchValue || this.comboInput.value;
1!
618
            const remoteValue = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
1!
619
            if (searchValue !== remoteValue) {
1!
UNCOV
620
                this.clear();
×
621
            }
622
            return;
1✔
623
        }
624

625
        const filtered = this.filteredData.find(this.findMatch);
19✔
626
        // selecting null in primitive data returns undefined as the search text is '', but the item is null
627
        if (filtered === undefined && this.selectedItem !== null || !super.selection.length) {
19✔
628
            this.clear();
16✔
629
        }
630
    }
631

632
    private getElementVal(element: any): string {
633
        const elementVal = this.displayKey ? element[this.displayKey] : element;
×
UNCOV
634
        return String(elementVal);
×
635
    }
636

637
    private clear(): void {
638
        this.clearSelection(true);
16✔
639
        const oldSelection = this.selection;
16✔
640
        if (this.selection !== oldSelection) {
16!
UNCOV
641
            this.comboInput.value = this._displayValue = this.searchValue = '';
×
642
        }
643
    }
644

645
    private isValid(value: any): boolean {
646
        if (this.formGroupDirective && value === null) {
325✔
647
            return false;
11✔
648
        }
649

650
        if (this.required) {
314✔
651
            return value !== null && value !== '' && value !== undefined
48✔
652
        }
653

654
        return value !== undefined;
266✔
655
    }
656
}
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