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

IgniteUI / igniteui-angular / 23353730325

20 Mar 2026 05:03PM UTC coverage: 9.784% (-79.5%) from 89.264%
23353730325

Pull #17069

github

web-flow
Merge cfa7e86d1 into a4dc50177
Pull Request #17069: fix(IgxGrid): Do not apply width constraint to groups.

921 of 16963 branches covered (5.43%)

Branch coverage included in aggregate %.

1 of 3 new or added lines in 1 file covered. (33.33%)

25213 existing lines in 340 files now uncovered.

3842 of 31721 relevant lines covered (12.11%)

6.13 hits per line

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

1.14
/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 the Combo's selection has changed */
26
export interface IComboSelectionChangedEventArgs extends IBaseEventArgs {
27
    /** An array containing the old selection values */
28
    oldValue: any[];
29
    /** An array containing the new selection items */
30
    newValue: any[];
31
    /** An array containing the old selection items */
32
    oldSelection: any[];
33
    /** An array containing the new selection items */
34
    newSelection: any[];
35
    /** An array containing the items added to the selection (if any) */
36
    added: any[];
37
    /** An array containing the items removed from the selection (if any) */
38
    removed: any[];
39
    /** The display text of 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 Combo's selection is changing */
46
export interface IComboSelectionChangingEventArgs extends IComboSelectionChangedEventArgs, IBaseCancelableEventArgs {}
47

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

54
export interface IComboItemAdditionEvent extends IBaseEventArgs, CancelableEventArgs {
55
    oldCollection: any[];
56
    addedItem: any;
57
    newCollection: any[];
58
}
59

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

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

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

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

159
    /**
160
     * Emitted when item selection is changed, after the selection completes
161
     *
162
     * ```html
163
     * <igx-combo (selectionChanged)='handleSelection()'></igx-combo>
164
     * ```
165
     */
166
    @Output()
UNCOV
167
    public selectionChanged = new EventEmitter<IComboSelectionChangedEventArgs>();
×
168

169
    /** @hidden @internal */
170
    @ViewChild(IgxComboDropDownComponent, { static: true })
171
    public dropdown: IgxComboDropDownComponent;
172

173
    /** @hidden @internal */
174
    public get filteredData(): any[] | null {
UNCOV
175
        return this.disableFiltering ? this.data : this._filteredData;
×
176
    }
177
    /** @hidden @internal */
178
    public set filteredData(val: any[] | null) {
UNCOV
179
        this._filteredData = this.groupKey ? (val || []).filter((e) => e.isHeader !== true) : val;
×
UNCOV
180
        this.checkMatch();
×
181
    }
182

UNCOV
183
    protected _prevInputValue = '';
×
184

185
    private _displayText: string;
186

187
    constructor() {
UNCOV
188
        super();
×
UNCOV
189
        this.comboAPI.register(this);
×
190
    }
191

192
    @HostListener('keydown.ArrowDown', ['$event'])
193
    @HostListener('keydown.Alt.ArrowDown', ['$event'])
194
    public onArrowDown(event: Event) {
UNCOV
195
        event.preventDefault();
×
UNCOV
196
        event.stopPropagation();
×
UNCOV
197
        this.open();
×
198
    }
199

200
    @HostListener('keydown.Escape', ['$event'])
201
    public onEscape(event: Event) {
UNCOV
202
        if (this.collapsed) {
×
UNCOV
203
            this.deselectAllItems(true, event);
×
204
        }
205
    }
206

207
    /** @hidden @internal */
208
    public get displaySearchInput(): boolean {
UNCOV
209
        return !this.disableFiltering || this.allowCustomValues;
×
210
    }
211

212
    /**
213
     * @hidden @internal
214
     */
215
    public handleKeyUp(event: KeyboardEvent): void {
216
        // TODO: use PlatformUtil for keyboard navigation
UNCOV
217
        if (event.key === 'ArrowDown' || event.key === 'Down') {
×
UNCOV
218
            this.dropdown.focusedItem = this.dropdown.items[0];
×
UNCOV
219
            this.dropdownContainer.nativeElement.focus();
×
UNCOV
220
        } else if (event.key === 'Escape' || event.key === 'Esc') {
×
UNCOV
221
            this.toggle();
×
222
        }
223
    }
224

225
    /**
226
     * @hidden @internal
227
     */
228
    public handleSelectAll(evt) {
UNCOV
229
        if (evt.checked) {
×
UNCOV
230
            this.selectAllItems();
×
231
        } else {
UNCOV
232
            this.deselectAllItems();
×
233
        }
234
    }
235

236
    /**
237
     * @hidden @internal
238
     */
239
    public writeValue(value: any[]): void {
UNCOV
240
        const selection = Array.isArray(value) ? value.filter(x => x !== undefined) : [];
×
UNCOV
241
        const oldSelection = this.selection;
×
UNCOV
242
        this.selectionService.select_items(this.id, selection, true);
×
UNCOV
243
        this.cdr.markForCheck();
×
UNCOV
244
        this._displayValue = this.createDisplayText(this.selection, oldSelection);
×
UNCOV
245
        this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
×
246
    }
247

248
    /** @hidden @internal */
249
    public ngDoCheck(): void {
UNCOV
250
        if (this.data?.length && this.selection.length) {
×
UNCOV
251
            this._displayValue = this._displayText || this.createDisplayText(this.selection, []);
×
UNCOV
252
            this._value = this.valueKey ? this.selection.map(item => item[this.valueKey]) : this.selection;
×
253
        }
254
    }
255

256
    /**
257
     * @hidden
258
     */
259
    public getEditElement(): HTMLElement {
UNCOV
260
        return this.comboInput.nativeElement;
×
261
    }
262

263
    /**
264
     * @hidden @internal
265
     */
266
    public get context(): any {
267
        return {
×
268
            $implicit: this
269
        };
270
    }
271

272
    /**
273
     * @hidden @internal
274
     */
275
    public handleClearItems(event: Event): void {
UNCOV
276
        if (this.disabled) {
×
UNCOV
277
            return;
×
278
        }
UNCOV
279
        this.deselectAllItems(true, event);
×
UNCOV
280
        if (this.collapsed) {
×
UNCOV
281
            this.getEditElement().focus();
×
282
        } else {
UNCOV
283
            this.focusSearchInput(true);
×
284
        }
UNCOV
285
        event.stopPropagation();
×
286
    }
287

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

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

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

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

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

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

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

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

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

456
    protected createDisplayText(newSelection: any[], oldSelection: any[]) {
UNCOV
457
        const selection = this.valueKey ? newSelection.map(item => item[this.valueKey]) : newSelection;
×
UNCOV
458
        return this.isRemote
×
459
            ? this.getRemoteSelection(selection, oldSelection)
460
            : this.concatDisplayText(newSelection);
461
    }
462

463
    protected getSearchPlaceholderText(): string {
UNCOV
464
        return this.searchPlaceholder ||
×
465
            (this.disableFiltering ? this.resourceStrings.igx_combo_addCustomValues_placeholder : this.resourceStrings.igx_combo_filter_search_placeholder);
×
466
    }
467

468
    /** Returns a string that should be populated in the combo's text box */
469
    private concatDisplayText(selection: any[]): string {
UNCOV
470
        const value = this.displayKey !== null && this.displayKey !== undefined ?
×
UNCOV
471
            selection.map(entry => entry[this.displayKey]).join(', ') :
×
472
            selection.join(', ');
UNCOV
473
        return value;
×
474
    }
475
}
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