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

IgniteUI / igniteui-angular / 20960087204

13 Jan 2026 02:19PM UTC coverage: 12.713% (-78.8%) from 91.5%
20960087204

Pull #16746

github

web-flow
Merge 9afce6e5d into a967f087e
Pull Request #16746: fix(csv): export summaries - master

1008 of 16803 branches covered (6.0%)

19 of 23 new or added lines in 2 files covered. (82.61%)

24693 existing lines in 336 files now uncovered.

3985 of 31345 relevant lines covered (12.71%)

2.49 hits per line

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

1.94
/projects/igniteui-angular/combo/src/combo/combo.component.ts
1
import { NgClass, NgTemplateOutlet } from '@angular/common';
2
import { AfterViewInit, Component, OnInit, OnDestroy, ViewChild, Input, Output, EventEmitter, HostListener, DoCheck, booleanAttribute } from '@angular/core';
3

4
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
5

6
import {
7
    IBaseEventArgs,
8
    IBaseCancelableEventArgs,
9
    CancelableEventArgs,
10
    EditorProvider
11
} from 'igniteui-angular/core';
12
import { IgxForOfDirective } from 'igniteui-angular/directives';
13
import { IgxRippleDirective } from 'igniteui-angular/directives';
14
import { IgxButtonDirective } from 'igniteui-angular/directives';
15
import { IgxComboItemComponent } from './combo-item.component';
16
import { IgxComboDropDownComponent } from './combo-dropdown.component';
17
import { IgxComboFilteringPipe, IgxComboGroupingPipe } from './combo.pipes';
18
import { IGX_COMBO_COMPONENT, IgxComboBaseDirective } from './combo.common';
19
import { IgxComboAddItemComponent } from './combo-add-item.component';
20
import { IgxComboAPIService } from './combo.api';
21
import { IgxInputGroupComponent, IgxInputDirective, IgxReadOnlyInputDirective, IgxSuffixDirective } from 'igniteui-angular/input-group';
22
import { IgxIconComponent } from 'igniteui-angular/icon';
23
import { IgxDropDownItemNavigationDirective } from 'igniteui-angular/drop-down';
24

25
/** Event emitted when an igx-combo's selection is changing */
26
export interface IComboSelectionChangingEventArgs extends IBaseCancelableEventArgs {
27
    /** An array containing the values that are currently selected */
28
    oldValue: any[];
29
    /** An array containing the values that will be selected after this event */
30
    newValue: any[];
31
    /** An array containing the items that are currently selected */
32
    oldSelection: any[];
33
    /** An array containing the items that will be selected after this event */
34
    newSelection: any[];
35
    /** An array containing the items that will be added to the selection (if any) */
36
    added: any[];
37
    /** An array containing the items that will be removed from the selection (if any) */
38
    removed: any[];
39
    /** The text that will be displayed in the combo text box */
40
    displayText: string;
41
    /** The user interaction that triggered the selection change */
42
    event?: Event;
43
}
44

45
/** Event emitted when the igx-combo's search input changes */
46
export interface IComboSearchInputEventArgs extends IBaseCancelableEventArgs {
47
    /** The text that has been typed into the search input */
48
    searchText: string;
49
}
50

51
export interface IComboItemAdditionEvent extends IBaseEventArgs, CancelableEventArgs {
52
    oldCollection: any[];
53
    addedItem: any;
54
    newCollection: any[];
55
}
56

57
/**
58
 * When called with sets A & B, returns A - B (as array);
59
 *
60
 * @hidden
61
 */
62
const diffInSets = (set1: Set<any>, set2: Set<any>): any[] => {
3✔
UNCOV
63
    const results = [];
×
UNCOV
64
    set1.forEach(entry => {
×
UNCOV
65
        if (!set2.has(entry)) {
×
UNCOV
66
            results.push(entry);
×
67
        }
68
    });
UNCOV
69
    return results;
×
70
};
71

72
/**
73
 *  Represents a drop-down list that provides editable functionalities, allowing users to choose an option from a predefined list.
74
 *
75
 * @igxModule IgxComboModule
76
 * @igxTheme igx-combo-theme
77
 * @igxKeywords combobox, combo selection
78
 * @igxGroup Grids & Lists
79
 *
80
 * @remarks
81
 * It provides the ability to filter items as well as perform selection with the provided data.
82
 * Additionally, it exposes keyboard navigation and custom styling capabilities.
83
 * @example
84
 * ```html
85
 * <igx-combo [itemsMaxHeight]="250" [data]="locationData"
86
 *  [displayKey]="'field'" [valueKey]="'field'"
87
 *  placeholder="Location(s)" searchPlaceholder="Search...">
88
 * </igx-combo>
89
 * ```
90
 */
91
@Component({
92
    selector: 'igx-combo',
93
    templateUrl: 'combo.component.html',
94
    providers: [
95
        IgxComboAPIService,
96
        { provide: IGX_COMBO_COMPONENT, useExisting: IgxComboComponent },
97
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxComboComponent, multi: true }
98
    ],
99
    imports: [
100
        NgTemplateOutlet,
101
        NgClass,
102
        FormsModule,
103
        IgxInputGroupComponent,
104
        IgxInputDirective,
105
        IgxSuffixDirective,
106
        IgxIconComponent,
107
        IgxComboDropDownComponent,
108
        IgxDropDownItemNavigationDirective,
109
        IgxForOfDirective,
110
        IgxComboItemComponent,
111
        IgxComboAddItemComponent,
112
        IgxButtonDirective,
113
        IgxRippleDirective,
114
        IgxReadOnlyInputDirective,
115
        IgxComboFilteringPipe,
116
        IgxComboGroupingPipe
117
    ]
118
})
119
export class IgxComboComponent extends IgxComboBaseDirective implements AfterViewInit, ControlValueAccessor, OnInit,
3✔
120
    OnDestroy, DoCheck, EditorProvider {
121
    /**
122
     * Whether the combo's search box should be focused after the dropdown is opened.
123
     * When `false`, the combo's list item container will be focused instead
124
     */
125
    @Input({ transform: booleanAttribute })
UNCOV
126
    public autoFocusSearch = true;
×
127

128

129
    /**
130
     * Defines the placeholder value for the combo dropdown search field
131
     *
132
     * @deprecated in version 18.2.0. Replaced with values in the localization resource strings.
133
     *
134
     * ```typescript
135
     * // get
136
     * let myComboSearchPlaceholder = this.combo.searchPlaceholder;
137
     * ```
138
     *
139
     * ```html
140
     * <!--set-->
141
     * <igx-combo [searchPlaceholder]='newPlaceHolder'></igx-combo>
142
     * ```
143
     */
144
    @Input()
145
    public searchPlaceholder: string;
146

147
    /**
148
     * Emitted when item selection is changing, before the selection completes
149
     *
150
     * ```html
151
     * <igx-combo (selectionChanging)='handleSelection()'></igx-combo>
152
     * ```
153
     */
154
    @Output()
UNCOV
155
    public selectionChanging = new EventEmitter<IComboSelectionChangingEventArgs>();
×
156

157
    /** @hidden @internal */
158
    @ViewChild(IgxComboDropDownComponent, { static: true })
159
    public dropdown: IgxComboDropDownComponent;
160

161
    /** @hidden @internal */
162
    public get filteredData(): any[] | null {
UNCOV
163
        return this.disableFiltering ? this.data : this._filteredData;
×
164
    }
165
    /** @hidden @internal */
166
    public set filteredData(val: any[] | null) {
UNCOV
167
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
×
UNCOV
168
        this.checkMatch();
×
169
    }
170

UNCOV
171
    protected _prevInputValue = '';
×
172

173
    private _displayText: string;
174

175
    constructor() {
UNCOV
176
        super();
×
UNCOV
177
        this.comboAPI.register(this);
×
178
    }
179

180
    @HostListener('keydown.ArrowDown', ['$event'])
181
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
182
    public onArrowDown(event: Event) {
UNCOV
183
        event.preventDefault();
×
UNCOV
184
        event.stopPropagation();
×
UNCOV
185
        this.open();
×
186
    }
187

188
    /** @hidden @internal */
189
    public get displaySearchInput(): boolean {
UNCOV
190
        return !this.disableFiltering || this.allowCustomValues;
×
191
    }
192

193
    /**
194
     * @hidden @internal
195
     */
196
    public handleKeyUp(event: KeyboardEvent): void {
197
        // TODO: use PlatformUtil for keyboard navigation
UNCOV
198
        if (event.key === 'ArrowDown' || event.key === 'Down') {
×
UNCOV
199
            this.dropdown.focusedItem = this.dropdown.items[0];
×
UNCOV
200
            this.dropdownContainer.nativeElement.focus();
×
UNCOV
201
        } else if (event.key === 'Escape' || event.key === 'Esc') {
×
UNCOV
202
            this.toggle();
×
203
        }
204
    }
205

206
    /**
207
     * @hidden @internal
208
     */
209
    public handleSelectAll(evt) {
UNCOV
210
        if (evt.checked) {
×
UNCOV
211
            this.selectAllItems();
×
212
        } else {
UNCOV
213
            this.deselectAllItems();
×
214
        }
215
    }
216

217
    /**
218
     * @hidden @internal
219
     */
220
    public writeValue(value: any[]): void {
UNCOV
221
        const selection = Array.isArray(value) ? value.filter(x => x !== undefined) : [];
×
UNCOV
222
        const oldSelection = this.selection;
×
UNCOV
223
        this.selectionService.select_items(this.id, selection, true);
×
UNCOV
224
        this.cdr.markForCheck();
×
UNCOV
225
        this._displayValue = this.createDisplayText(this.selection, oldSelection);
×
UNCOV
226
        this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
×
227
    }
228

229
    /** @hidden @internal */
230
    public ngDoCheck(): void {
UNCOV
231
        if (this.data?.length && this.selection.length) {
×
UNCOV
232
            this._displayValue = this._displayText || this.createDisplayText(this.selection, []);
×
UNCOV
233
            this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
×
234
        }
235
    }
236

237
    /**
238
     * @hidden
239
     */
240
    public getEditElement(): HTMLElement {
UNCOV
241
        return this.comboInput.nativeElement;
×
242
    }
243

244
    /**
245
     * @hidden @internal
246
     */
247
    public get context(): any {
248
        return {
×
249
            $implicit: this
250
        };
251
    }
252

253
    /**
254
     * @hidden @internal
255
     */
256
    public clearInput(event: Event): void {
UNCOV
257
        this.deselectAllItems(true, event);
×
UNCOV
258
        if (this.collapsed) {
×
UNCOV
259
            this.getEditElement().focus();
×
260
        } else {
UNCOV
261
            this.focusSearchInput(true);
×
262
        }
UNCOV
263
        event.stopPropagation();
×
264
    }
265

266
    /**
267
     * @hidden @internal
268
     */
269
    public handleClearItems(event: Event): void {
UNCOV
270
        if (this.disabled) {
×
UNCOV
271
            return;
×
272
        }
UNCOV
273
        this.clearInput(event);
×
274
    }
275

276
    /**
277
     * @hidden @internal
278
     */
279
    public handleClearKeyDown(eventArgs: KeyboardEvent) {
UNCOV
280
        if (eventArgs.key === 'Enter' || eventArgs.key === ' ') {
×
UNCOV
281
            eventArgs.preventDefault();
×
UNCOV
282
            this.clearInput(eventArgs);
×
283
        }
284
    }
285

286
    /**
287
     * Select defined items
288
     *
289
     * @param newItems new items to be selected
290
     * @param clearCurrentSelection if true clear previous selected items
291
     * ```typescript
292
     * this.combo.select(["New York", "New Jersey"]);
293
     * ```
294
     */
295
    public select(newItems: Array<any>, clearCurrentSelection?: boolean, event?: Event) {
UNCOV
296
        if (newItems) {
×
UNCOV
297
            const newSelection = this.selectionService.add_items(this.id, newItems, clearCurrentSelection);
×
UNCOV
298
            this.setSelection(newSelection, event);
×
299
        }
300
    }
301

302
    /**
303
     * Deselect defined items
304
     *
305
     * @param items items to deselected
306
     * ```typescript
307
     * this.combo.deselect(["New York", "New Jersey"]);
308
     * ```
309
     */
310
    public deselect(items: Array<any>, event?: Event) {
UNCOV
311
        if (items) {
×
UNCOV
312
            const newSelection = this.selectionService.delete_items(this.id, items);
×
UNCOV
313
            this.setSelection(newSelection, event);
×
314
        }
315
    }
316

317
    /**
318
     * Select all (filtered) items
319
     *
320
     * @param ignoreFilter if set to true, selects all items, otherwise selects only the filtered ones.
321
     * ```typescript
322
     * this.combo.selectAllItems();
323
     * ```
324
     */
325
    public selectAllItems(ignoreFilter?: boolean, event?: Event) {
UNCOV
326
        const allVisible = this.selectionService.get_all_ids(ignoreFilter ? this.data : this.filteredData, this.valueKey);
×
UNCOV
327
        const newSelection = this.selectionService.add_items(this.id, allVisible);
×
UNCOV
328
        this.setSelection(newSelection, event);
×
329
    }
330

331
    /**
332
     * Deselect all (filtered) items
333
     *
334
     * @param ignoreFilter if set to true, deselects all items, otherwise deselects only the filtered ones.
335
     * ```typescript
336
     * this.combo.deselectAllItems();
337
     * ```
338
     */
339
    public deselectAllItems(ignoreFilter?: boolean, event?: Event): void {
UNCOV
340
        let newSelection = this.selectionService.get_empty();
×
UNCOV
341
        if (this.filteredData.length !== this.data.length && !ignoreFilter) {
×
UNCOV
342
            newSelection = this.selectionService.delete_items(this.id, this.selectionService.get_all_ids(this.filteredData, this.valueKey));
×
343
        }
UNCOV
344
        this.setSelection(newSelection, event);
×
345
    }
346

347
    /**
348
     * Selects/Deselects a single item
349
     *
350
     * @param itemID the itemID of the specific item
351
     * @param select If the item should be selected (true) or deselected (false)
352
     *
353
     * Without specified valueKey;
354
     * ```typescript
355
     * this.combo.valueKey = null;
356
     * const items: { field: string, region: string}[] = data;
357
     * this.combo.setSelectedItem(items[0], true);
358
     * ```
359
     * With specified valueKey;
360
     * ```typescript
361
     * this.combo.valueKey = 'field';
362
     * const items: { field: string, region: string}[] = data;
363
     * this.combo.setSelectedItem('Connecticut', true);
364
     * ```
365
     */
366
    public setSelectedItem(itemID: any, select = true, event?: Event): void {
×
UNCOV
367
        if (itemID === undefined) {
×
368
            return;
×
369
        }
UNCOV
370
        if (select) {
×
UNCOV
371
            this.select([itemID], false, event);
×
372
        } else {
UNCOV
373
            this.deselect([itemID], event);
×
374
        }
375
    }
376

377
    /** @hidden @internal */
378
    public handleOpened() {
UNCOV
379
        this.triggerCheck();
×
380

381
        // Disabling focus of the search input should happen only when drop down opens.
382
        // During keyboard navigation input should receive focus, even the autoFocusSearch is disabled.
383
        // That is why in such cases focusing of the dropdownContainer happens outside focusSearchInput method.
UNCOV
384
        if (this.autoFocusSearch) {
×
UNCOV
385
            this.focusSearchInput(true);
×
386
        } else {
UNCOV
387
            this.dropdownContainer.nativeElement.focus();
×
388
        }
UNCOV
389
        this.opened.emit({ owner: this });
×
390
    }
391

392
    /** @hidden @internal */
393
    public focusSearchInput(opening?: boolean): void {
UNCOV
394
        if (this.displaySearchInput && this.searchInput) {
×
UNCOV
395
            this.searchInput.nativeElement.focus();
×
396
        } else {
UNCOV
397
            if (opening) {
×
UNCOV
398
                this.dropdownContainer.nativeElement.focus();
×
399
            } else {
400
                this.comboInput.nativeElement.focus();
×
401
                this.toggle();
×
402
            }
403
        }
404
    }
405

406
    protected setSelection(selection: Set<any>, event?: Event): void {
UNCOV
407
        const currentSelection = this.selectionService.get(this.id);
×
UNCOV
408
        const removed = this.convertKeysToItems(diffInSets(currentSelection, selection));
×
UNCOV
409
        const added = this.convertKeysToItems(diffInSets(selection, currentSelection));
×
UNCOV
410
        const newValue = Array.from(selection);
×
UNCOV
411
        const oldValue = Array.from(currentSelection || []);
×
UNCOV
412
        const newSelection = this.convertKeysToItems(newValue);
×
UNCOV
413
        const oldSelection = this.convertKeysToItems(oldValue);
×
UNCOV
414
        const displayText = this.createDisplayText(this.convertKeysToItems(newValue), oldValue);
×
UNCOV
415
        const args: IComboSelectionChangingEventArgs = {
×
416
            newValue,
417
            oldValue,
418
            newSelection,
419
            oldSelection,
420
            added,
421
            removed,
422
            event,
423
            owner: this,
424
            displayText,
425
            cancel: false
426
        };
UNCOV
427
        this.selectionChanging.emit(args);
×
UNCOV
428
        if (!args.cancel) {
×
UNCOV
429
            this.selectionService.select_items(this.id, args.newValue, true);
×
UNCOV
430
            this._value = args.newValue;
×
UNCOV
431
            if (displayText !== args.displayText) {
×
UNCOV
432
                this._displayValue = this._displayText = args.displayText;
×
433
            } else {
UNCOV
434
                this._displayValue = this.createDisplayText(this.selection, args.oldSelection);
×
435
            }
UNCOV
436
            this._onChangeCallback(args.newValue);
×
UNCOV
437
        } else if (this.isRemote) {
×
UNCOV
438
            this.registerRemoteEntries(diffInSets(selection, currentSelection), false);
×
439
        }
440
    }
441

442
    protected createDisplayText(newSelection: any[], oldSelection: any[]) {
UNCOV
443
        const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
×
UNCOV
444
        return this.isRemote
×
445
            ? this.getRemoteSelection(selection, oldSelection)
446
            : this.concatDisplayText(newSelection);
447
    }
448

449
    protected getSearchPlaceholderText(): string {
UNCOV
450
        return this.searchPlaceholder ||
×
451
            (this.disableFiltering ? this.resourceStrings.igx_combo_addCustomValues_placeholder : this.resourceStrings.igx_combo_filter_search_placeholder);
×
452
    }
453

454
    /** Returns a string that should be populated in the combo's text box */
455
    private concatDisplayText(selection: any[]): string {
UNCOV
456
        const value = this.displayKey !== null && this.displayKey !== undefined ?
×
UNCOV
457
            selection.map(entry => entry[this.displayKey]).join(', ') :
×
458
            selection.join(', ');
UNCOV
459
        return value;
×
460
    }
461
}
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