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

IgniteUI / igniteui-angular / 24559825320

17 Apr 2026 10:11AM UTC coverage: 89.474%. First build
24559825320

Pull #17203

github

web-flow
Merge 624e05de5 into a42734e5d
Pull Request #17203: fix(select): support igx-select-item inside nested @for/@if control flow blocks - 20.1.x

14016 of 16432 branches covered (85.3%)

Branch coverage included in aggregate %.

23 of 24 new or added lines in 1 file covered. (95.83%)

28025 of 30555 relevant lines covered (91.72%)

35639.77 hits per line

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

95.45
/projects/igniteui-angular/src/lib/select/select.component.ts
1
import {
2
    AfterContentChecked,
3
    AfterContentInit,
4
    AfterViewInit,
5
    booleanAttribute,
6
    ChangeDetectorRef,
7
    Component,
8
    ContentChild,
9
    ContentChildren,
10
    Directive,
11
    ElementRef,
12
    EventEmitter,
13
    forwardRef,
14
    HostBinding,
15
    Inject,
16
    Injector,
17
    Input,
18
    OnDestroy,
19
    OnInit,
20
    Optional,
21
    Output,
22
    QueryList,
23
    TemplateRef,
24
    ViewChild,
25
    DOCUMENT,
26
    ViewChildren
27
} from '@angular/core';
28
import { NgTemplateOutlet } from '@angular/common';
29
import { AbstractControl, ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
30
import { noop } from 'rxjs';
31
import { takeUntil } from 'rxjs/operators';
32

33
import { EditorProvider } from '../core/edit-provider';
34
import { IgxSelectionAPIService } from '../core/selection';
35
import { IBaseCancelableBrowserEventArgs, IBaseEventArgs } from '../core/utils';
36
import { IgxLabelDirective } from '../directives/label/label.directive';
37
import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base';
38
import { IGX_DROPDOWN_BASE, ISelectionEventArgs, Navigate } from '../drop-down/drop-down.common';
39
import { IgxInputGroupComponent } from '../input-group/input-group.component';
40
import { AbsoluteScrollStrategy } from '../services/overlay/scroll/absolute-scroll-strategy';
41
import { OverlaySettings } from '../services/overlay/utilities';
42
import { IgxDropDownComponent } from './../drop-down/drop-down.component';
43
import { IgxSelectItemComponent } from './select-item.component';
44
import { SelectPositioningStrategy } from './select-positioning-strategy';
45
import { IgxSelectBase } from './select.common';
46
import { IgxHintDirective, IgxInputGroupType, IgxPrefixDirective, IGX_INPUT_GROUP_TYPE } from '../input-group/public_api';
47
import { ToggleViewCancelableEventArgs, ToggleViewEventArgs, IgxToggleDirective } from '../directives/toggle/toggle.directive';
48
import { IgxOverlayService } from '../services/overlay/overlay';
49
import { IgxIconComponent } from '../icon/icon.component';
50
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
51
import { IgxSelectItemNavigationDirective } from './select-navigation.directive';
52
import { IgxInputDirective, IgxInputState } from '../directives/input/input.directive';
53
import { IgxReadOnlyInputDirective } from '../directives/input/read-only-input.directive';
54

55
/** @hidden @internal */
56
@Directive({
57
    selector: '[igxSelectToggleIcon]',
58
    standalone: true
59
})
60
export class IgxSelectToggleIconDirective {
3✔
61
}
62

63
/** @hidden @internal */
64
@Directive({
65
    selector: '[igxSelectHeader]',
66
    standalone: true
67
})
68
export class IgxSelectHeaderDirective {
3✔
69
}
70

71
/** @hidden @internal */
72
@Directive({
73
    selector: '[igxSelectFooter]',
74
    standalone: true
75
})
76
export class IgxSelectFooterDirective {
3✔
77
}
78

79
/**
80
 * **Ignite UI for Angular Select** -
81
 * [Documentation](https://www.infragistics.com/products/ignite-ui-angular/angular/components/select)
82
 *
83
 * The `igxSelect` provides an input with dropdown list allowing selection of a single item.
84
 *
85
 * Example:
86
 * ```html
87
 * <igx-select #select1 [placeholder]="'Pick One'">
88
 *   <label igxLabel>Select Label</label>
89
 *   <igx-select-item *ngFor="let item of items" [value]="item.field">
90
 *     {{ item.field }}
91
 *   </igx-select-item>
92
 * </igx-select>
93
 * ```
94
 */
95
@Component({
96
    selector: 'igx-select',
97
    templateUrl: './select.component.html',
98
    providers: [
99
        { provide: NG_VALUE_ACCESSOR, useExisting: IgxSelectComponent, multi: true },
100
        { provide: IGX_DROPDOWN_BASE, useExisting: IgxSelectComponent }
101
    ],
102
    styles: [`
103
        :host {
104
            display: block;
105
        }
106
    `],
107
    imports: [IgxInputGroupComponent, IgxInputDirective, IgxSelectItemNavigationDirective, IgxSuffixDirective, IgxReadOnlyInputDirective, NgTemplateOutlet, IgxIconComponent, IgxToggleDirective]
108
})
109
export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelectBase, ControlValueAccessor,
3✔
110
    AfterContentInit, OnInit, AfterViewInit, OnDestroy, EditorProvider, AfterContentChecked {
111

112
    /** @hidden @internal */
113
    @ViewChild('inputGroup', { read: IgxInputGroupComponent, static: true }) public inputGroup: IgxInputGroupComponent;
114

115
    /** @hidden @internal */
116
    @ViewChild('input', { read: IgxInputDirective, static: true }) public input: IgxInputDirective;
117

118
    /** @hidden @internal */
119
    @ContentChildren(forwardRef(() => IgxSelectItemComponent), { descendants: true })
6✔
120
    public override children: QueryList<IgxSelectItemComponent>;
121

122
    @ContentChildren(IgxPrefixDirective, { descendants: true })
123
    protected prefixes: QueryList<IgxPrefixDirective>;
124

125
    @ContentChildren(IgxSuffixDirective, { descendants: true })
126
    protected suffixes: QueryList<IgxSuffixDirective>;
127

128
    @ViewChildren(IgxSuffixDirective)
129
    protected internalSuffixes: QueryList<IgxSuffixDirective>;
130

131
    /** @hidden @internal */
132
    @ContentChild(forwardRef(() => IgxLabelDirective), { static: true }) public label: IgxLabelDirective;
6✔
133

134
    /**
135
     * Sets input placeholder.
136
     *
137
     */
138
    @Input() public placeholder;
139

140

141
    /**
142
     * Disables the component.
143
     * ```html
144
     * <igx-select [disabled]="'true'"></igx-select>
145
     * ```
146
     */
147
    @Input({ transform: booleanAttribute }) public disabled = false;
1,152✔
148

149
    /**
150
     * Sets custom OverlaySettings `IgxSelectComponent`.
151
     * ```html
152
     * <igx-select [overlaySettings] = "customOverlaySettings"></igx-select>
153
     * ```
154
     */
155
    @Input()
156
    public overlaySettings: OverlaySettings;
157

158
    /** @hidden @internal */
159
    @HostBinding('style.maxHeight')
160
    public override maxHeight = '256px';
1,152✔
161

162
    /**
163
     * Emitted before the dropdown is opened
164
     *
165
     * ```html
166
     * <igx-select opening='handleOpening($event)'></igx-select>
167
     * ```
168
     */
169
    @Output()
170
    public override opening = new EventEmitter<IBaseCancelableBrowserEventArgs>();
1,152✔
171

172
    /**
173
     * Emitted after the dropdown is opened
174
     *
175
     * ```html
176
     * <igx-select (opened)='handleOpened($event)'></igx-select>
177
     * ```
178
     */
179
    @Output()
180
    public override opened = new EventEmitter<IBaseEventArgs>();
1,152✔
181

182
    /**
183
     * Emitted before the dropdown is closed
184
     *
185
     * ```html
186
     * <igx-select (closing)='handleClosing($event)'></igx-select>
187
     * ```
188
     */
189
    @Output()
190
    public override closing = new EventEmitter<IBaseCancelableBrowserEventArgs>();
1,152✔
191

192
    /**
193
     * Emitted after the dropdown is closed
194
     *
195
     * ```html
196
     * <igx-select (closed)='handleClosed($event)'></igx-select>
197
     * ```
198
     */
199
    @Output()
200
    public override closed = new EventEmitter<IBaseEventArgs>();
1,152✔
201

202
    /**
203
     * The custom template, if any, that should be used when rendering the select TOGGLE(open/close) button
204
     *
205
     * ```typescript
206
     * // Set in typescript
207
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
208
     * myComponent.select.toggleIconTemplate = myCustomTemplate;
209
     * ```
210
     * ```html
211
     * <!-- Set in markup -->
212
     *  <igx-select #select>
213
     *      ...
214
     *      <ng-template igxSelectToggleIcon let-collapsed>
215
     *          <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>
216
     *      </ng-template>
217
     *  </igx-select>
218
     * ```
219
     */
220
    @ContentChild(IgxSelectToggleIconDirective, { read: TemplateRef })
221
    public toggleIconTemplate: TemplateRef<any> = null;
1,152✔
222

223
    /**
224
     * The custom template, if any, that should be used when rendering the HEADER for the select items list
225
     *
226
     * ```typescript
227
     * // Set in typescript
228
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
229
     * myComponent.select.headerTemplate = myCustomTemplate;
230
     * ```
231
     * ```html
232
     * <!-- Set in markup -->
233
     *  <igx-select #select>
234
     *      ...
235
     *      <ng-template igxSelectHeader>
236
     *          <div class="select__header">
237
     *              This is a custom header
238
     *          </div>
239
     *      </ng-template>
240
     *  </igx-select>
241
     * ```
242
     */
243
    @ContentChild(IgxSelectHeaderDirective, { read: TemplateRef, static: false })
244
    public headerTemplate: TemplateRef<any> = null;
1,152✔
245

246
    /**
247
     * The custom template, if any, that should be used when rendering the FOOTER for the select items list
248
     *
249
     * ```typescript
250
     * // Set in typescript
251
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
252
     * myComponent.select.footerTemplate = myCustomTemplate;
253
     * ```
254
     * ```html
255
     * <!-- Set in markup -->
256
     *  <igx-select #select>
257
     *      ...
258
     *      <ng-template igxSelectFooter>
259
     *          <div class="select__footer">
260
     *              This is a custom footer
261
     *          </div>
262
     *      </ng-template>
263
     *  </igx-select>
264
     * ```
265
     */
266
    @ContentChild(IgxSelectFooterDirective, { read: TemplateRef, static: false })
267
    public footerTemplate: TemplateRef<any> = null;
1,152✔
268

269
    @ContentChild(IgxHintDirective, { read: ElementRef }) private hintElement: ElementRef;
270

271
    /** @hidden @internal */
272
    public override width: string;
273

274
    /** @hidden @internal do not use the drop-down container class */
275
    public override cssClass = false;
1,152✔
276

277
    /** @hidden @internal */
278
    public override allowItemsFocus = false;
1,152✔
279

280
    /** @hidden @internal */
281
    public override height: string;
282

283
    private ngControl: NgControl = null;
1,152✔
284
    private _overlayDefaults: OverlaySettings;
285
    private _value: any;
286
    private _type = null;
1,152✔
287

288
    /**
289
     * Gets/Sets the component value.
290
     *
291
     * ```typescript
292
     * // get
293
     * let selectValue = this.select.value;
294
     * ```
295
     *
296
     * ```typescript
297
     * // set
298
     * this.select.value = 'London';
299
     * ```
300
     * ```html
301
     * <igx-select [value]="value"></igx-select>
302
     * ```
303
     */
304
    @Input()
305
    public get value(): any {
306
        return this._value;
9,795✔
307
    }
308
    public set value(v: any) {
309
        if (this._value === v) {
2,456✔
310
            return;
308✔
311
        }
312
        this._value = v;
2,148✔
313
        this.setSelection(this.items.find(x => x.value === this.value));
3,269✔
314
    }
315

316
    /**
317
     * Sets how the select will be styled.
318
     * The allowed values are `line`, `box` and `border`. The input-group default is `line`.
319
     * ```html
320
     * <igx-select [type]="'box'"></igx-select>
321
     * ```
322
     */
323
    @Input()
324
    public get type(): IgxInputGroupType {
325
        return this._type || this._inputGroupType || 'line';
59,181✔
326
    }
327

328
    public set type(val: IgxInputGroupType) {
329
        this._type = val;
1,047✔
330
    }
331

332
    /** @hidden @internal */
333
    public get selectionValue() {
334
        const selectedItem = this.selectedItem;
29,589✔
335
        return selectedItem ? selectedItem.itemText : '';
29,589✔
336
    }
337

338
    /** @hidden @internal */
339
    public override get selectedItem(): IgxSelectItemComponent {
340
        return this.selection.first_item(this.id);
31,775✔
341
    }
342

343
    private _onChangeCallback: (_: any) => void = noop;
1,152✔
344
    private _onTouchedCallback: () => void = noop;
1,152✔
345

346
    constructor(
347
        elementRef: ElementRef,
348
        cdr: ChangeDetectorRef,
349
        @Inject(DOCUMENT) document: any,
350
        selection: IgxSelectionAPIService,
351
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
1,152✔
352
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) private _inputGroupType: IgxInputGroupType,
1,152✔
353
        private _injector: Injector,
1,152✔
354
    ) {
355
        super(elementRef, cdr, document, selection);
1,152✔
356
    }
357

358
    //#region ControlValueAccessor
359

360
    /** @hidden @internal */
361
    public writeValue = (value: any) => {
1,152✔
362
        this.value = value;
2,437✔
363
    };
364

365
    /** @hidden @internal */
366
    public registerOnChange(fn: any): void {
367
        this._onChangeCallback = fn;
1,054✔
368
    }
369

370
    /** @hidden @internal */
371
    public registerOnTouched(fn: any): void {
372
        this._onTouchedCallback = fn;
1,054✔
373
    }
374

375
    /** @hidden @internal */
376
    public setDisabledState(isDisabled: boolean): void {
377
        this.disabled = isDisabled;
1,212✔
378
    }
379
    //#endregion
380

381
    /** @hidden @internal */
382
    public getEditElement(): HTMLInputElement {
383
        return this.input.nativeElement;
1,152✔
384
    }
385

386
    /** @hidden @internal */
387
    public override selectItem(newSelection: IgxDropDownItemBaseDirective, event?) {
388
        const oldSelection = this.selectedItem ?? <IgxDropDownItemBaseDirective>{};
399✔
389

390
        if (newSelection === null || newSelection.disabled || newSelection.isHeader) {
399✔
391
            return;
2✔
392
        }
393

394
        if (newSelection === oldSelection) {
397✔
395
            this.toggleDirective.close();
24✔
396
            return;
24✔
397
        }
398

399
        const args: ISelectionEventArgs = { oldSelection, newSelection, cancel: false, owner: this };
373✔
400
        this.selectionChanging.emit(args);
373✔
401

402
        if (args.cancel) {
373✔
403
            return;
110✔
404
        }
405

406
        this.setSelection(newSelection);
263✔
407
        this._value = newSelection.value;
263✔
408

409
        if (event) {
263✔
410
            this.toggleDirective.close();
112✔
411
        }
412

413
        this.cdr.detectChanges();
263✔
414
        this._onChangeCallback(this.value);
263✔
415
    }
416

417
    /** @hidden @internal */
418
    public getFirstItemElement(): HTMLElement {
419
        return this.children.first.element.nativeElement;
85✔
420
    }
421

422
    /**
423
     * Opens the select
424
     *
425
     * ```typescript
426
     * this.select.open();
427
     * ```
428
     */
429
    public override open(overlaySettings?: OverlaySettings) {
430
        if (this.disabled || this.items.length === 0) {
305✔
431
            return;
3✔
432
        }
433
        if (!this.selectedItem) {
302✔
434
            this.navigateFirst();
210✔
435
        }
436

437
        super.open(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
302✔
438
    }
439

440
    public inputGroupClick(event: MouseEvent, overlaySettings?: OverlaySettings) {
441
        const targetElement = event.target as HTMLElement;
234✔
442

443
        if (this.hintElement && targetElement.contains(this.hintElement.nativeElement)) {
234!
444
            return;
×
445
        }
446
        this.toggle(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
234✔
447
    }
448

449
    /** @hidden @internal */
450
    public ngAfterContentInit() {
451
        this._overlayDefaults = {
1,151✔
452
            target: this.getEditElement(),
453
            modal: false,
454
            positionStrategy: new SelectPositioningStrategy(this),
455
            scrollStrategy: new AbsoluteScrollStrategy(),
456
            excludeFromOutsideClick: [this.inputGroup.element.nativeElement as HTMLElement]
457
        };
458

459
        // Initial pass — moves items that Angular's content projection could not place
460
        // (e.g. items nested inside @for > @if control flow blocks).
461
        this.moveItemsToScrollContainer();
1,151✔
462

463
        const changes$ = this.children.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
1,151✔
464
            this.moveItemsToScrollContainer();
865✔
465
            this.setSelection(this.items.find(x => x.value === this.value));
3,262✔
466
            this.cdr.detectChanges();
865✔
467
        });
468
        Promise.resolve().then(() => {
1,151✔
469
            if (!changes$.closed) {
1,151✔
470
                this.children.notifyOnChanges();
826✔
471
            }
472
        });
473
    }
474

475
    /**
476
     * Event handlers
477
     *
478
     * @hidden @internal
479
     */
480
    public handleOpening(e: ToggleViewCancelableEventArgs) {
481
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
302✔
482
        this.opening.emit(args);
302✔
483

484
        e.cancel = args.cancel;
302✔
485
        if (args.cancel) {
302!
486
            return;
×
487
        }
488
    }
489

490
    /** @hidden @internal */
491
    public override onToggleContentAppended(event: ToggleViewEventArgs) {
492
        const info = this.overlayService.getOverlayById(event.id);
301✔
493
        if (info?.settings?.positionStrategy instanceof SelectPositioningStrategy) {
301!
494
            return;
×
495
        }
496
        super.onToggleContentAppended(event);
301✔
497
    }
498

499
    /** @hidden @internal */
500
    public handleOpened() {
501
        this.updateItemFocus();
294✔
502
        this.opened.emit({ owner: this });
294✔
503
    }
504

505
    /** @hidden @internal */
506
    public handleClosing(e: ToggleViewCancelableEventArgs) {
507
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
254✔
508
        this.closing.emit(args);
254✔
509
        e.cancel = args.cancel;
254✔
510
    }
511

512
    /** @hidden @internal */
513
    public handleClosed() {
514
        this.focusItem(false);
251✔
515
        this.closed.emit({ owner: this });
251✔
516
    }
517

518
    /** @hidden @internal */
519
    public onBlur(): void {
520
        this._onTouchedCallback();
165✔
521
        if (this.ngControl && this.ngControl.invalid) {
165✔
522
            this.input.valid = IgxInputState.INVALID;
3✔
523
        } else {
524
            this.input.valid = IgxInputState.INITIAL;
162✔
525
        }
526
    }
527

528
    /** @hidden @internal */
529
    public onFocus(): void {
530
        this._onTouchedCallback();
234✔
531
    }
532

533
    /**
534
     * @hidden @internal
535
     */
536
    public override ngOnInit() {
537
        this.ngControl = this._injector.get<NgControl>(NgControl, null);
1,152✔
538
    }
539

540
    /**
541
     * @hidden @internal
542
     */
543
    public override ngAfterViewInit() {
544
        super.ngAfterViewInit();
1,151✔
545

546
        if (this.ngControl) {
1,151✔
547
            this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged.bind(this));
1,049✔
548
            this.manageRequiredAsterisk();
1,049✔
549
        }
550

551
        this.cdr.detectChanges();
1,151✔
552
    }
553

554
    /** @hidden @internal */
555
    public ngAfterContentChecked() {
556
        if (this.inputGroup && this.prefixes?.length > 0) {
14,003✔
557
            this.inputGroup.prefixes = this.prefixes;
762✔
558
        }
559

560
        if (this.inputGroup) {
14,003✔
561
            const suffixesArray = this.suffixes?.toArray() ?? [];
14,003!
562
            const internalSuffixesArray = this.internalSuffixes?.toArray() ?? [];
14,003✔
563
            const mergedSuffixes = new QueryList<IgxSuffixDirective>();
14,003✔
564
            mergedSuffixes.reset([
14,003✔
565
                ...suffixesArray,
566
                ...internalSuffixesArray
567
            ]);
568
            this.inputGroup.suffixes = mergedSuffixes;
14,003✔
569
        }
570
    }
571

572
    /** @hidden @internal */
573
    public get toggleIcon(): string {
574
        return this.collapsed ? 'input_expand' : 'input_collapse';
29,589✔
575
    }
576

577
    /**
578
     * @hidden @internal
579
     * Prevent input blur - closing the items container on Header/Footer Template click.
580
     */
581
    public mousedownHandler(event) {
582
        event.preventDefault();
×
583
    }
584

585
    protected onStatusChanged() {
586
        this.manageRequiredAsterisk();
1,385✔
587
        if (this.ngControl && !this.disabled && this.isTouchedOrDirty) {
1,385✔
588
            if (this.hasValidators && this.inputGroup.isFocused) {
516✔
589
                this.input.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
2✔
590
            } else {
591
                // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
592
                this.input.valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
514✔
593
            }
594
        } else {
595
            this.input.valid = IgxInputState.INITIAL;
869✔
596
        }
597
    }
598

599
    private get isTouchedOrDirty(): boolean {
600
        return (this.ngControl.control.touched || this.ngControl.control.dirty);
1,290✔
601
    }
602

603
    private get hasValidators(): boolean {
604
        return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
516✔
605
    }
606

607
    protected override navigate(direction: Navigate, currentIndex?: number) {
608
        if (this.collapsed && this.selectedItem) {
323✔
609
            this.navigateItem(this.selectedItem.itemIndex);
41✔
610
        }
611
        super.navigate(direction, currentIndex);
323✔
612
    }
613

614
    protected manageRequiredAsterisk(): void {
615
        const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
2,434✔
616
        let isRequired = false;
2,434✔
617

618
        if (this.ngControl && this.ngControl.control.validator) {
2,434✔
619
            const error = this.ngControl.control.validator({} as AbstractControl);
17✔
620
            isRequired = !!(error && error.required);
17✔
621
        }
622

623
        this.inputGroup.isRequired = isRequired;
2,434✔
624

625
        if (this.input?.nativeElement) {
2,434✔
626
            this.input.nativeElement.setAttribute('aria-required', isRequired.toString());
2,434✔
627
        }
628

629
        // Handle validator removal case
630
        if (!isRequired && !hasRequiredHTMLAttribute) {
2,434✔
631
            this.input.valid = IgxInputState.INITIAL;
2,413✔
632
        }
633

634
        this.cdr.markForCheck();
2,434✔
635
    }
636

637
    /**
638
     * Moves any igx-select-item elements that Angular's content projection could not
639
     * place in the scroll container (e.g. items nested inside @for > @if control flow).
640
     * Items already inside the container (directly or within a projected group) are skipped.
641
     * If an item is inside an igx-select-item-group that is itself unprojected, the whole
642
     * group is moved instead of detaching the item from it.
643
     * Insertion position is derived from @ContentChildren order so that mixing
644
     * normally-projected items with control-flow items preserves the template order.
645
     */
646
    private moveItemsToScrollContainer(): void {
647
        if (!this.children?.length || !this.scrollContainer) {
2,016✔
648
            return;
100✔
649
        }
650
        const container = this.scrollContainer;
1,916✔
651

652
        // Build an ordered list of top-level nodes (group or standalone item)
653
        // based on @ContentChildren order, deduplicating group entries.
654
        const orderedTopLevelNodes: HTMLElement[] = [];
1,916✔
655
        const seenNodes = new Set<HTMLElement>();
1,916✔
656
        for (const child of this.children) {
1,916✔
657
            const el: HTMLElement = child.element.nativeElement;
11,499✔
658
            const groupEl = el.closest('igx-select-item-group') as HTMLElement | null;
11,499✔
659
            const topLevelNode = groupEl ?? el;
11,499✔
660
            if (!seenNodes.has(topLevelNode)) {
11,499✔
661
                seenNodes.add(topLevelNode);
11,454✔
662
                orderedTopLevelNodes.push(topLevelNode);
11,454✔
663
            }
664
        }
665

666
        let nextReferenceNode: HTMLElement | null = null;
1,916✔
667
        for (let index = orderedTopLevelNodes.length - 1; index >= 0; index--) {
1,916✔
668
            const node = orderedTopLevelNodes[index];
11,454✔
669
            if (node.parentElement === container) {
11,454✔
670
                nextReferenceNode = node;
11,418✔
671
                continue;
11,418✔
672
            }
673
            if (container.contains(node)) {
36!
NEW
674
                continue;
×
675
            }
676
            container.insertBefore(node, nextReferenceNode);
36✔
677
            nextReferenceNode = node;
36✔
678
        }
679
    }
680

681
    private setSelection(item: IgxDropDownItemBaseDirective) {
682
        if (item && item.value !== undefined && item.value !== null) {
3,276✔
683
            this.selection.set(this.id, new Set([item]));
1,737✔
684
        } else {
685
            this.selection.clear(this.id);
1,539✔
686
        }
687
    }
688
}
689

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