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

IgniteUI / igniteui-angular / 28358286454

29 Jun 2026 08:17AM UTC coverage: 90.127% (-0.05%) from 90.174%
28358286454

Pull #17246

github

web-flow
Merge 21687d939 into 07bdcd752
Pull Request #17246: fix(pivot-grid): fix date format based on the localization

14921 of 17392 branches covered (85.79%)

Branch coverage included in aggregate %.

29 of 32 new or added lines in 4 files covered. (90.63%)

735 existing lines in 76 files now uncovered.

29992 of 32441 relevant lines covered (92.45%)

34632.46 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, ChangeDetectionStrategy } 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
    changeDetection: ChangeDetectionStrategy.Eager,
61
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxTextSelectionDirective, IgxSuffixDirective, NgTemplateOutlet, IgxIconComponent, IgxComboDropDownComponent, IgxDropDownItemNavigationDirective, IgxForOfDirective, IgxComboItemComponent, IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, IgxComboFilteringPipe, IgxComboGroupingPipe]
62
})
63
export class IgxSimpleComboComponent extends IgxComboBaseDirective implements ControlValueAccessor, AfterViewInit, DoCheck {
3✔
64
    private platformUtil = inject(PlatformUtil);
145✔
65
    private formGroupDirective = inject(FormGroupDirective, { optional: true });
145✔
66

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

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

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

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

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

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

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

114
    /** @hidden @internal */
115
    public composing = false;
145✔
116

117
    private _updateInput = true;
145✔
118

119
    private _collapsing = false;
145✔
120

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

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

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

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

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

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

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

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

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

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

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

264
        super.ngAfterViewInit();
127✔
265
    }
266

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

651
        if (this.required) {
320✔
652
            return value !== null && value !== '' && value !== undefined
59✔
653
        }
654

655
        return value !== undefined;
261✔
656
    }
657
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc