• 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

0.41
/projects/igniteui-angular/src/lib/simple-combo/simple-combo.component.ts
1
import { DOCUMENT, NgIf, NgTemplateOutlet } from '@angular/common';
2
import {
3
    AfterViewInit, ChangeDetectorRef, Component, DoCheck, ElementRef, EventEmitter, HostListener, Inject, Injector,
4
    Optional, Output, ViewChild
5
} from '@angular/core';
6
import { ControlValueAccessor, FormGroupDirective, NG_VALUE_ACCESSOR } from '@angular/forms';
7
import { takeUntil } from 'rxjs/operators';
8

9
import { IgxComboAddItemComponent } from '../combo/combo-add-item.component';
10
import { IgxComboDropDownComponent } from '../combo/combo-dropdown.component';
11
import { IgxComboItemComponent } from '../combo/combo-item.component';
12
import { IgxComboAPIService } from '../combo/combo.api';
13
import { IgxComboBaseDirective, IGX_COMBO_COMPONENT } from '../combo/combo.common';
14
import { IgxSelectionAPIService } from '../core/selection';
15
import { CancelableEventArgs, IBaseCancelableBrowserEventArgs, IBaseEventArgs, PlatformUtil } from '../core/utils';
16
import { IgxButtonDirective } from '../directives/button/button.directive';
17
import { IgxForOfDirective } from '../directives/for-of/for_of.directive';
18
import { IgxRippleDirective } from '../directives/ripple/ripple.directive';
19
import { IgxTextSelectionDirective } from '../directives/text-selection/text-selection.directive';
20
import { IgxIconService } from '../icon/icon.service';
21
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';
22
import { IgxComboFilteringPipe, IgxComboGroupingPipe } from '../combo/combo.pipes';
23
import { IgxDropDownItemNavigationDirective } from '../drop-down/drop-down-navigation.directive';
24
import { IgxIconComponent } from '../icon/icon.component';
25
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
26
import { IgxInputDirective } from '../directives/input/input.directive';
27
import { IgxInputGroupComponent } from '../input-group/input-group.component';
28

29
/** Emitted when an igx-simple-combo's selection is changing.  */
30
export interface ISimpleComboSelectionChangingEventArgs extends CancelableEventArgs, IBaseEventArgs {
31
    /** An object which represents the value that is currently selected */
32
    oldValue: any;
33
    /** An object which represents the value that will be selected after this event */
34
    newValue: any;
35
    /** An object which represents the item that is currently selected */
36
    oldSelection: any;
37
    /** An object which represents the item that will be selected after this event */
38
    newSelection: any;
39
    /** The text that will be displayed in the combo text box */
40
    displayText: string;
41
}
42

43
/**
44
 * Represents a drop-down list that provides filtering functionality, allowing users to choose a single option from a predefined list.
45
 *
46
 * @igxModule IgxSimpleComboModule
47
 * @igxTheme igx-combo-theme
48
 * @igxKeywords combobox, single combo selection
49
 * @igxGroup Grids & Lists
50
 *
51
 * @remarks
52
 * It provides the ability to filter items as well as perform single selection on the provided data.
53
 * Additionally, it exposes keyboard navigation and custom styling capabilities.
54
 * @example
55
 * ```html
56
 * <igx-simple-combo [itemsMaxHeight]="250" [data]="locationData"
57
 *  [displayKey]="'field'" [valueKey]="'field'"
58
 *  placeholder="Location" searchPlaceholder="Search...">
59
 * </igx-simple-combo>
60
 * ```
61
 */
62
@Component({
63
    selector: 'igx-simple-combo',
64
    templateUrl: 'simple-combo.component.html',
65
    providers: [
66
        IgxComboAPIService,
67
        { provide: IGX_COMBO_COMPONENT, useExisting: IgxSimpleComboComponent },
68
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxSimpleComboComponent, multi: true }
69
    ],
70
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxTextSelectionDirective, NgIf, IgxSuffixDirective, NgTemplateOutlet, IgxIconComponent, IgxComboDropDownComponent, IgxDropDownItemNavigationDirective, IgxForOfDirective, IgxComboItemComponent, IgxComboAddItemComponent, IgxButtonDirective, IgxRippleDirective, IgxComboFilteringPipe, IgxComboGroupingPipe]
71
})
72
export class IgxSimpleComboComponent extends IgxComboBaseDirective implements ControlValueAccessor, AfterViewInit, DoCheck {
2✔
73
    /** @hidden @internal */
74
    @ViewChild(IgxComboDropDownComponent, { static: true })
75
    public dropdown: IgxComboDropDownComponent;
76

77
    /** @hidden @internal */
78
    @ViewChild(IgxComboAddItemComponent)
79
    public addItem: IgxComboAddItemComponent;
80

81
    /**
82
     * Emitted when item selection is changing, before the selection completes
83
     *
84
     * ```html
85
     * <igx-simple-combo (selectionChanging)='handleSelection()'></igx-simple-combo>
86
     * ```
87
     */
88
    @Output()
UNCOV
89
    public selectionChanging = new EventEmitter<ISimpleComboSelectionChangingEventArgs>();
×
90

91
    @ViewChild(IgxTextSelectionDirective, { static: true })
92
    private textSelection: IgxTextSelectionDirective;
93

94
    public override get value(): any {
UNCOV
95
        return this._value[0];
×
96
    }
97

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

110
    /** @hidden @internal */
UNCOV
111
    public composing = false;
×
112

UNCOV
113
    private _updateInput = true;
×
114

UNCOV
115
    private _collapsing = false;
×
116

117
    /** @hidden @internal */
118
    public get filteredData(): any[] | null {
UNCOV
119
        return this._filteredData;
×
120
    }
121
    /** @hidden @internal */
122
    public set filteredData(val: any[] | null) {
UNCOV
123
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
×
UNCOV
124
        this.checkMatch();
×
125
    }
126

127
    /** @hidden @internal */
128
    public override get searchValue(): string {
UNCOV
129
        return this._searchValue;
×
130
    }
131
    public override set searchValue(val: string) {
UNCOV
132
        this._searchValue = val;
×
133
    }
134

135
    private get selectedItem(): any {
UNCOV
136
        return this.selectionService.get(this.id).values().next().value;
×
137
    }
138

139
    protected get hasSelectedItem(): boolean {
UNCOV
140
        return !!this.selectionService.get(this.id).size;
×
141
    }
142

143
    constructor(elementRef: ElementRef,
144
        cdr: ChangeDetectorRef,
145
        selectionService: IgxSelectionAPIService,
146
        comboAPI: IgxComboAPIService,
UNCOV
147
        private platformUtil: PlatformUtil,
×
148
        @Inject(DOCUMENT) document: any,
149
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) _inputGroupType: IgxInputGroupType,
150
        @Optional() _injector: Injector,
151
        @Optional() @Inject(IgxIconService) _iconService?: IgxIconService,
UNCOV
152
        @Optional() private formGroupDirective?: FormGroupDirective
×
153
    ) {
UNCOV
154
        super(
×
155
            elementRef,
156
            cdr,
157
            selectionService,
158
            comboAPI,
159
            document,
160
            _inputGroupType,
161
            _injector,
162
            _iconService
163
        );
UNCOV
164
        this.comboAPI.register(this);
×
165
    }
166

167
    /** @hidden @internal */
168
    @HostListener('keydown.ArrowDown', ['$event'])
169
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
170
    public onArrowDown(event: Event): void {
UNCOV
171
        if (this.collapsed) {
×
UNCOV
172
            event.preventDefault();
×
UNCOV
173
            event.stopPropagation();
×
UNCOV
174
            this.open();
×
175
        } else {
UNCOV
176
            if (this.virtDir.igxForOf.length > 0 && !this.hasSelectedItem) {
×
UNCOV
177
                this.dropdown.navigateNext();
×
UNCOV
178
                this.dropdownContainer.nativeElement.focus();
×
UNCOV
179
            } else if (this.allowCustomValues) {
×
UNCOV
180
                this.addItem?.element.nativeElement.focus();
×
181
            }
182
        }
183
    }
184

185
    /**
186
     * Select a defined item
187
     *
188
     * @param item the item to be selected
189
     * ```typescript
190
     * this.combo.select("New York");
191
     * ```
192
     */
193
    public select(item: any): void {
UNCOV
194
        if (item !== undefined) {
×
UNCOV
195
            const newSelection = this.selectionService.add_items(this.id, item instanceof Array ? item : [item], true);
×
UNCOV
196
            this.setSelection(newSelection);
×
197
        }
198
    }
199

200
    /**
201
     * Deselect the currently selected item
202
     *
203
     * @param item the items to be deselected
204
     * ```typescript
205
     * this.combo.deselect("New York");
206
     * ```
207
     */
208
    public deselect(): void {
UNCOV
209
        this.clearSelection();
×
210
    }
211

212
    /** @hidden @internal */
213
    public writeValue(value: any): void {
UNCOV
214
        const oldSelection = super.selection;
×
UNCOV
215
        this.selectionService.select_items(this.id, this.isValid(value) ? [value] : [], true);
×
UNCOV
216
        this.cdr.markForCheck();
×
UNCOV
217
        this._displayValue = this.createDisplayText(super.selection, oldSelection);
×
UNCOV
218
        this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
×
UNCOV
219
        this.filterValue = this._displayValue?.toString() || '';
×
220
    }
221

222
    /** @hidden @internal */
223
    public override ngAfterViewInit(): void {
UNCOV
224
        this.virtDir.contentSizeChange.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
225
            if (super.selection.length > 0) {
×
UNCOV
226
                const index = this.virtDir.igxForOf.findIndex(e => {
×
UNCOV
227
                    let current = e? e[this.valueKey] : undefined;
×
UNCOV
228
                    if (this.valueKey === null || this.valueKey === undefined) {
×
229
                        current = e;
×
230
                    }
UNCOV
231
                    return current === super.selection[0];
×
232
                });
UNCOV
233
                if (!this.isRemote) {
×
234
                    // navigate to item only if we have local data
235
                    // as with remote data this will fiddle with igxFor's scroll handler
236
                    // and will trigger another chunk load which will break the visualization
237
                    this.dropdown.navigateItem(index);
×
238
                }
239
            }
240
        });
UNCOV
241
        this.dropdown.opening.pipe(takeUntil(this.destroy$)).subscribe((args) => {
×
UNCOV
242
            if (args.cancel) {
×
243
                return;
×
244
            }
UNCOV
245
            this._collapsing = false;
×
UNCOV
246
            const filtered = this.filteredData.find(this.findAllMatches);
×
UNCOV
247
            if (filtered === undefined || filtered === null) {
×
UNCOV
248
                this.filterValue = this.searchValue = this.comboInput.value;
×
UNCOV
249
                return;
×
250
            }
251
        });
UNCOV
252
        this.dropdown.opened.pipe(takeUntil(this.destroy$)).subscribe(() => {
×
UNCOV
253
            if (this.composing) {
×
UNCOV
254
                this.comboInput.focus();
×
255
            }
256
        });
UNCOV
257
        this.dropdown.closing.pipe(takeUntil(this.destroy$)).subscribe((args) => {
×
UNCOV
258
            if (args.cancel) {
×
259
                return;
×
260
            }
UNCOV
261
            if (this.getEditElement() && !args.event) {
×
UNCOV
262
                this._collapsing = true;
×
263
            } else {
264
                this.clearOnBlur();
×
265
                this._onTouchedCallback();
×
266
            }
UNCOV
267
            this.comboInput.focus();
×
268
        });
269

270
        // in reactive form the control is not present initially
271
        // and sets the selection to an invalid value in writeValue method
UNCOV
272
        if (!this.isValid(this.selectedItem)) {
×
UNCOV
273
            this.selectionService.clear(this.id);
×
UNCOV
274
            this._displayValue = '';
×
275
        }
276

UNCOV
277
        super.ngAfterViewInit();
×
278
    }
279

280
    /** @hidden @internal */
281
    public ngDoCheck(): void {
UNCOV
282
        if (this.data?.length && super.selection.length && !this._displayValue) {
×
UNCOV
283
            this._displayValue = this.createDisplayText(super.selection, []);
×
UNCOV
284
            this._value = this.valueKey ? super.selection.map(item => item[this.valueKey]) : super.selection;
×
285
        }
286
    }
287

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

325
    /** @hidden @internal */
326
    public handleInputClick(): void {
UNCOV
327
        if (this.collapsed) {
×
UNCOV
328
            this.open();
×
UNCOV
329
            this.comboInput.focus();
×
330
        }
331
    }
332

333
    /** @hidden @internal */
334
    public override handleKeyDown(event: KeyboardEvent): void {
UNCOV
335
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
×
UNCOV
336
            const filtered = this.filteredData.find(this.findAllMatches);
×
UNCOV
337
            if (filtered === null || filtered === undefined) {
×
338
                return;
×
339
            }
UNCOV
340
            if (!this.dropdown.collapsed) {
×
UNCOV
341
                const focusedItem = this.dropdown.focusedItem;
×
UNCOV
342
                if (focusedItem && !focusedItem.isHeader) {
×
UNCOV
343
                    this.select(focusedItem.itemID);
×
UNCOV
344
                    event.preventDefault();
×
UNCOV
345
                    event.stopPropagation();
×
UNCOV
346
                    this.close();
×
347
                } else {
UNCOV
348
                    event.preventDefault();
×
UNCOV
349
                    event.stopPropagation();
×
UNCOV
350
                    this.comboInput.focus();
×
351
                }
352
            }
353
            // manually trigger text selection as it will not be triggered during editing
UNCOV
354
            this.textSelection.trigger();
×
UNCOV
355
            return;
×
356
        }
UNCOV
357
        if (event.key === this.platformUtil.KEYMAP.BACKSPACE
×
358
            || event.key === this.platformUtil.KEYMAP.DELETE) {
UNCOV
359
            this._updateInput = false;
×
UNCOV
360
            this.clearSelection(true);
×
361
        }
UNCOV
362
        if (!this.collapsed && event.key === this.platformUtil.KEYMAP.TAB) {
×
UNCOV
363
            const filtered = this.filteredData.find(this.findAllMatches);
×
UNCOV
364
            if (filtered === null || filtered === undefined) {
×
UNCOV
365
                this.clearOnBlur();
×
UNCOV
366
                this.close();
×
UNCOV
367
                return;
×
368
            }
UNCOV
369
            const focusedItem = this.dropdown.focusedItem;
×
UNCOV
370
            if (focusedItem && !focusedItem.isHeader) {
×
UNCOV
371
                this.select(focusedItem.itemID);
×
UNCOV
372
                this.close();
×
UNCOV
373
                this.textSelection.trigger();
×
374
            } else {
UNCOV
375
                this.clearOnBlur();
×
UNCOV
376
                this.close();
×
377
            }
378
        }
UNCOV
379
        this.composing = false;
×
UNCOV
380
        super.handleKeyDown(event);
×
381
    }
382

383
    /** @hidden @internal */
384
    public handleKeyUp(event: KeyboardEvent): void {
UNCOV
385
        if (event.key === this.platformUtil.KEYMAP.ARROW_DOWN) {
×
UNCOV
386
            this.dropdown.focusedItem = this.hasSelectedItem && this.filteredData.length > 0
×
387
                ? this.dropdown.items.find(i => i.itemID === this.selectedItem)
×
388
                : this.dropdown.items[0];
UNCOV
389
            this.dropdownContainer.nativeElement.focus();
×
390
        }
391
    }
392

393
    /** @hidden @internal */
394
    public handleItemKeyDown(event: KeyboardEvent): void {
UNCOV
395
        if (event.key === this.platformUtil.KEYMAP.ARROW_UP && event.altKey) {
×
396
            this.close();
×
397
            this.comboInput.focus();
×
398
            return;
×
399
        }
UNCOV
400
        if (event.key === this.platformUtil.KEYMAP.ENTER) {
×
401
            this.comboInput.focus();
×
402
        }
403
    }
404

405
    /** @hidden @internal */
406
    public handleItemClick(): void {
UNCOV
407
        this.close();
×
UNCOV
408
        this.comboInput.focus();
×
409
    }
410

411
    /** @hidden @internal */
412
    public override onBlur(): void {
413
        // when clicking the toggle button to close the combo and immediately clicking outside of it
414
        // the collapsed state is not modified as the dropdown is still not closed
UNCOV
415
        if (this.collapsed || this._collapsing) {
×
UNCOV
416
            this.clearOnBlur();
×
417
        }
UNCOV
418
        super.onBlur();
×
419
    }
420

421
    /** @hidden @internal */
422
    public getEditElement(): HTMLElement {
UNCOV
423
        return this.comboInput.nativeElement;
×
424
    }
425

426
    /** @hidden @internal */
427
    public handleClear(event: Event): void {
UNCOV
428
        if (this.disabled) {
×
UNCOV
429
            return;
×
430
        }
431

UNCOV
432
        const oldSelection = this.selection;
×
UNCOV
433
        this.clearSelection(true);
×
434

UNCOV
435
        if(!this.collapsed){
×
UNCOV
436
            this.focusSearchInput(true);
×
437
        }
UNCOV
438
        event.stopPropagation();
×
439

UNCOV
440
        if (this.selection !== oldSelection) {
×
UNCOV
441
            this.comboInput.value = this.filterValue = this.searchValue = '';
×
442
        }
443

UNCOV
444
        this.dropdown.focusedItem = null;
×
UNCOV
445
        this.composing = false;
×
UNCOV
446
        this.comboInput.focus();
×
447
    }
448

449
    /** @hidden @internal */
450
    public handleOpened(): void {
UNCOV
451
        this.triggerCheck();
×
UNCOV
452
        if (!this.comboInput.focused) {
×
UNCOV
453
            this.dropdownContainer.nativeElement.focus();
×
454
        }
UNCOV
455
        this.opened.emit({ owner: this });
×
456
    }
457

458
    /** @hidden @internal */
459
    public override handleClosing(e: IBaseCancelableBrowserEventArgs): void {
UNCOV
460
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
×
UNCOV
461
        this.closing.emit(args);
×
UNCOV
462
        e.cancel = args.cancel;
×
UNCOV
463
        if (e.cancel) {
×
UNCOV
464
            return;
×
465
        }
466

UNCOV
467
        this.composing = false;
×
468
        // explicitly update selection and trigger text selection so that we don't have to force CD
UNCOV
469
        this.textSelection.selected = true;
×
UNCOV
470
        this.textSelection.trigger();
×
471
    }
472

473
    /** @hidden @internal */
474
    public focusSearchInput(opening?: boolean): void {
UNCOV
475
        if (opening) {
×
UNCOV
476
            this.dropdownContainer.nativeElement.focus();
×
477
        } else {
UNCOV
478
            this.comboInput.nativeElement.focus();
×
479
        }
480
    }
481

482
    /** @hidden @internal */
483
    public override onClick(event: Event): void {
UNCOV
484
        super.onClick(event);
×
UNCOV
485
        if (this.comboInput.value.length === 0) {
×
UNCOV
486
            this.virtDir.scrollTo(0);
×
487
        }
488
    }
489

UNCOV
490
    protected findAllMatches = (element: any): boolean => {
×
UNCOV
491
        const value = this.displayKey ? element[this.displayKey] : element;
×
UNCOV
492
        if (value === null || value === undefined || value === '') {
×
493
            // we can accept null, undefined and empty strings as empty display values
UNCOV
494
            return true;
×
495
        }
UNCOV
496
        const searchValue = this.searchValue || this.comboInput.value;
×
UNCOV
497
        return !!searchValue && value.toString().toLowerCase().includes(searchValue.toLowerCase());
×
498
    };
499

500
    protected setSelection(newSelection: any): void {
UNCOV
501
        const newValueAsArray = newSelection ? Array.from(newSelection) as IgxComboItemComponent[] : [];
×
UNCOV
502
        const oldValueAsArray = Array.from(this.selectionService.get(this.id) || []);
×
UNCOV
503
        const newItems = this.convertKeysToItems(newValueAsArray);
×
UNCOV
504
        const oldItems = this.convertKeysToItems(oldValueAsArray);
×
UNCOV
505
        const displayText = this.createDisplayText(this.convertKeysToItems(newValueAsArray), oldValueAsArray);
×
UNCOV
506
        const args: ISimpleComboSelectionChangingEventArgs = {
×
507
            newValue: newValueAsArray[0],
508
            oldValue: oldValueAsArray[0],
509
            newSelection: newItems[0],
510
            oldSelection: oldItems[0],
511
            displayText,
512
            owner: this,
513
            cancel: false
514
        };
UNCOV
515
        if (args.newSelection !== args.oldSelection) {
×
UNCOV
516
            this.selectionChanging.emit(args);
×
517
        }
518
        // TODO: refactor below code as it sets the selection and the display text
UNCOV
519
        if (!args.cancel) {
×
UNCOV
520
            let argsSelection = this.isValid(args.newValue)
×
521
                ? args.newValue
522
                : [];
UNCOV
523
            argsSelection = Array.isArray(argsSelection) ? argsSelection : [argsSelection];
×
UNCOV
524
            this.selectionService.select_items(this.id, argsSelection, true);
×
UNCOV
525
            this._value = argsSelection;
×
UNCOV
526
            if (this._updateInput) {
×
UNCOV
527
                this.comboInput.value = this._displayValue = this.searchValue = displayText !== args.displayText
×
528
                    ? args.displayText
529
                    : this.createDisplayText(super.selection, [args.oldValue]);
530
            }
UNCOV
531
            this._onChangeCallback(args.newValue);
×
UNCOV
532
            this._updateInput = true;
×
UNCOV
533
        } else if (this.isRemote) {
×
UNCOV
534
            this.registerRemoteEntries(newValueAsArray, false);
×
535
        } else {
UNCOV
536
            args.displayText = this.createDisplayText(oldItems, []);
×
537

UNCOV
538
            const oldSelectionArray = args.oldSelection ? [args.oldSelection] : [];
×
UNCOV
539
            this.comboInput.value = this._displayValue = this.searchValue = this.createDisplayText(oldSelectionArray, []);
×
540

UNCOV
541
            if (this.isRemote) {
×
542
                this.registerRemoteEntries(newValueAsArray, false);
×
543
            }
544
        }
545
    }
546

547
    protected createDisplayText(newSelection: any[], oldSelection: any[]): string {
UNCOV
548
        if (this.isRemote) {
×
UNCOV
549
            const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
×
UNCOV
550
            return this.getRemoteSelection(selection, oldSelection);
×
551
        }
552

UNCOV
553
        if (this.displayKey !== null
×
554
            && this.displayKey !== undefined
555
            && newSelection.length > 0) {
UNCOV
556
            return newSelection.filter(e => e).map(e => e[this.displayKey])[0]?.toString() || '';
×
557
        }
558

UNCOV
559
        return newSelection[0]?.toString() || '';
×
560
    }
561

562
    protected override getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
UNCOV
563
        if (!newSelection.length) {
×
UNCOV
564
            this.registerRemoteEntries(oldSelection, false);
×
UNCOV
565
            return '';
×
566
        }
567

UNCOV
568
        this.registerRemoteEntries(oldSelection, false);
×
UNCOV
569
        this.registerRemoteEntries(newSelection);
×
UNCOV
570
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
×
571
    }
572

573
    /** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
574
    protected override registerRemoteEntries(ids: any[], add = true) {
×
UNCOV
575
        const selection = this.getValueDisplayPairs(ids)[0];
×
576

UNCOV
577
        if (add && selection) {
×
UNCOV
578
            this._remoteSelection[selection[this.valueKey]] = selection[this.displayKey].toString();
×
579
        } else {
UNCOV
580
            this._remoteSelection = {};
×
581
        }
582
    }
583

584
    private clearSelection(ignoreFilter?: boolean): void {
UNCOV
585
        let newSelection = this.selectionService.get_empty();
×
UNCOV
586
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
×
UNCOV
587
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
×
588
        }
UNCOV
589
        if (this.selectionService.get(this.id).size > 0 || this.comboInput.value.trim()) {
×
UNCOV
590
            this.setSelection(newSelection);
×
591
        }
592
    }
593

594
    private clearOnBlur(): void {
UNCOV
595
        if (this.isRemote) {
×
UNCOV
596
            const searchValue = this.searchValue || this.comboInput.value;
×
UNCOV
597
            const remoteValue = Object.keys(this._remoteSelection).map(e => this._remoteSelection[e])[0] || '';
×
UNCOV
598
            if (searchValue !== remoteValue) {
×
599
                this.clear();
×
600
            }
UNCOV
601
            return;
×
602
        }
603

UNCOV
604
        const filtered = this.filteredData.find(this.findMatch);
×
605
        // selecting null in primitive data returns undefined as the search text is '', but the item is null
UNCOV
606
        if (filtered === undefined && this.selectedItem !== null || !super.selection.length) {
×
UNCOV
607
            this.clear();
×
608
        }
609
    }
610

611
    private getElementVal(element: any): string {
612
        const elementVal = this.displayKey ? element[this.displayKey] : element;
×
613
        return String(elementVal);
×
614
    }
615

616
    private clear(): void {
UNCOV
617
        this.clearSelection(true);
×
UNCOV
618
        const oldSelection = this.selection;
×
UNCOV
619
        if (this.selection !== oldSelection) {
×
620
            this.comboInput.value = this._displayValue = this.searchValue = '';
×
621
        }
622
    }
623

624
    private isValid(value: any): boolean {
UNCOV
625
        if (this.formGroupDirective && value === null) {
×
UNCOV
626
            return false;
×
627
        }
628

UNCOV
629
        if (this.required) {
×
UNCOV
630
            return value !== null && value !== '' && value !== undefined
×
631
        }
632

UNCOV
633
        return value !== undefined;
×
634
    }
635
}
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