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

IgniteUI / igniteui-angular / 8187641769

07 Mar 2024 11:52AM UTC coverage: 91.838% (-0.02%) from 91.857%
8187641769

push

github

web-flow
fix(filtering): pass overlay settings in GridType (#13975)

12523 of 14595 branches covered (85.8%)

25521 of 27789 relevant lines covered (91.84%)

30277.72 hits per line

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

96.15
/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
} from '@angular/core';
26
import { NgIf, NgTemplateOutlet } from '@angular/common';
27
import { AbstractControl, ControlValueAccessor, NgControl, NG_VALUE_ACCESSOR } from '@angular/forms';
28
import { noop } from 'rxjs';
29
import { takeUntil } from 'rxjs/operators';
30

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

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

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

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

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

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

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

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

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

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

127
    /** @hidden @internal */
128
    @ContentChild(forwardRef(() => IgxLabelDirective), { static: true }) public label: IgxLabelDirective;
4✔
129

130
    /**
131
     * An @Input property that sets input placeholder.
132
     *
133
     */
134
    @Input() public placeholder;
135

136

137
    /**
138
     * An @Input property that disables the `IgxSelectComponent`.
139
     * ```html
140
     * <igx-select [disabled]="'true'"></igx-select>
141
     * ```
142
     */
143
    @Input({ transform: booleanAttribute }) public disabled = false;
476✔
144

145
    /**
146
     * An @Input property that sets custom OverlaySettings `IgxSelectComponent`.
147
     * ```html
148
     * <igx-select [overlaySettings] = "customOverlaySettings"></igx-select>
149
     * ```
150
     */
151
    @Input()
152
    public overlaySettings: OverlaySettings;
153

154
    /** @hidden @internal */
155
    @HostBinding('style.maxHeight')
156
    public override maxHeight = '256px';
476✔
157

158
    /**
159
     * Emitted before the dropdown is opened
160
     *
161
     * ```html
162
     * <igx-select opening='handleOpening($event)'></igx-select>
163
     * ```
164
     */
165
    @Output()
166
    public override opening = new EventEmitter<IBaseCancelableBrowserEventArgs>();
476✔
167

168
    /**
169
     * Emitted after the dropdown is opened
170
     *
171
     * ```html
172
     * <igx-select (opened)='handleOpened($event)'></igx-select>
173
     * ```
174
     */
175
    @Output()
176
    public override opened = new EventEmitter<IBaseEventArgs>();
476✔
177

178
    /**
179
     * Emitted before the dropdown is closed
180
     *
181
     * ```html
182
     * <igx-select (closing)='handleClosing($event)'></igx-select>
183
     * ```
184
     */
185
    @Output()
186
    public override closing = new EventEmitter<IBaseCancelableBrowserEventArgs>();
476✔
187

188
    /**
189
     * Emitted after the dropdown is closed
190
     *
191
     * ```html
192
     * <igx-select (closed)='handleClosed($event)'></igx-select>
193
     * ```
194
     */
195
    @Output()
196
    public override closed = new EventEmitter<IBaseEventArgs>();
476✔
197

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

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

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

265
    @ContentChild(IgxHintDirective, { read: ElementRef }) private hintElement: ElementRef;
266

267
    /** @hidden @internal */
268
    public override width: string;
269

270
    /** @hidden @internal do not use the drop-down container class */
271
    public override cssClass = false;
476✔
272

273
    /** @hidden @internal */
274
    public override allowItemsFocus = false;
476✔
275

276
    /** @hidden @internal */
277
    public override height: string;
278

279
    private ngControl: NgControl = null;
476✔
280
    private _overlayDefaults: OverlaySettings;
281
    private _value: any;
282
    private _type = null;
476✔
283

284
    /**
285
     * An @Input property that gets/sets the component value.
286
     *
287
     * ```typescript
288
     * // get
289
     * let selectValue = this.select.value;
290
     * ```
291
     *
292
     * ```typescript
293
     * // set
294
     * this.select.value = 'London';
295
     * ```
296
     * ```html
297
     * <igx-select [value]="value"></igx-select>
298
     * ```
299
     */
300
    @Input()
301
    public get value(): any {
302
        return this._value;
4,040✔
303
    }
304
    public set value(v: any) {
305
        if (this._value === v) {
920✔
306
            return;
113✔
307
        }
308
        this._value = v;
807✔
309
        this.setSelection(this.items.find(x => x.value === this.value));
1,660✔
310
    }
311

312
    /**
313
     * An @Input property that sets how the select will be styled.
314
     * The allowed values are `line`, `box` and `border`. The input-group default is `line`.
315
     * ```html
316
     * <igx-select [type]="'box'"></igx-select>
317
     * ```
318
     */
319
    @Input()
320
    public get type(): IgxInputGroupType {
321
        return this._type || this._inputGroupType || 'line';
6,149✔
322
    }
323

324
    public set type(val: IgxInputGroupType) {
325
        this._type = val;
381✔
326
    }
327

328
    /** @hidden @internal */
329
    public get selectionValue() {
330
        const selectedItem = this.selectedItem;
6,146✔
331
        return selectedItem ? selectedItem.itemText : '';
6,146✔
332
    }
333

334
    /** @hidden @internal */
335
    public override get selectedItem(): IgxSelectItemComponent {
336
        return this.selection.first_item(this.id);
7,435✔
337
    }
338

339
    private _onChangeCallback: (_: any) => void = noop;
476✔
340
    private _onTouchedCallback: () => void = noop;
476✔
341

342
    constructor(
343
        elementRef: ElementRef,
344
        cdr: ChangeDetectorRef,
345
        selection: IgxSelectionAPIService,
346
        @Inject(IgxOverlayService) protected overlayService: IgxOverlayService,
476✔
347
        @Optional() @Inject(DisplayDensityToken) _displayDensityOptions: IDisplayDensityOptions,
348
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) private _inputGroupType: IgxInputGroupType,
476✔
349
        private _injector: Injector) {
476✔
350
        super(elementRef, cdr, selection, _displayDensityOptions);
476✔
351
    }
352

353
    //#region ControlValueAccessor
354

355
    /** @hidden @internal */
356
    public writeValue = (value: any) => {
476✔
357
        this.value = value;
904✔
358
    };
359

360
    /** @hidden @internal */
361
    public registerOnChange(fn: any): void {
362
        this._onChangeCallback = fn;
421✔
363
    }
364

365
    /** @hidden @internal */
366
    public registerOnTouched(fn: any): void {
367
        this._onTouchedCallback = fn;
421✔
368
    }
369

370
    /** @hidden @internal */
371
    public setDisabledState(isDisabled: boolean): void {
372
        this.disabled = isDisabled;
491✔
373
    }
374
    //#endregion
375

376
    /** @hidden @internal */
377
    public getEditElement(): HTMLInputElement {
378
        return this.input.nativeElement;
476✔
379
    }
380

381
    /** @hidden @internal */
382
    public override selectItem(newSelection: IgxDropDownItemBaseDirective, event?) {
383
        const oldSelection = this.selectedItem ?? <IgxDropDownItemBaseDirective>{};
247✔
384

385
        if (newSelection === null || newSelection.disabled || newSelection.isHeader) {
247✔
386
            return;
2✔
387
        }
388

389
        if (newSelection === oldSelection) {
245✔
390
            this.toggleDirective.close();
15✔
391
            return;
15✔
392
        }
393

394
        const args: ISelectionEventArgs = { oldSelection, newSelection, cancel: false, owner: this };
230✔
395
        this.selectionChanging.emit(args);
230✔
396

397
        if (args.cancel) {
230!
398
            return;
×
399
        }
400

401
        this.setSelection(newSelection);
230✔
402
        this._value = newSelection.value;
230✔
403

404
        if (event) {
230✔
405
            this.toggleDirective.close();
95✔
406
        }
407

408
        this.cdr.detectChanges();
230✔
409
        this._onChangeCallback(this.value);
230✔
410
    }
411

412
    /** @hidden @internal */
413
    public getFirstItemElement(): HTMLElement {
414
        return this.children.first.element.nativeElement;
49✔
415
    }
416

417
    /**
418
     * Opens the select
419
     *
420
     * ```typescript
421
     * this.select.open();
422
     * ```
423
     */
424
    public override open(overlaySettings?: OverlaySettings) {
425
        if (this.disabled || this.items.length === 0) {
163✔
426
            return;
3✔
427
        }
428
        if (!this.selectedItem) {
160✔
429
            this.navigateFirst();
118✔
430
        }
431

432
        super.open(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
160✔
433
    }
434

435
    public inputGroupClick(event: MouseEvent, overlaySettings?: OverlaySettings) {
436
        const targetElement = event.target as HTMLElement;
92✔
437

438
        if (this.hintElement && targetElement.contains(this.hintElement.nativeElement)) {
92!
439
            return;
×
440
        }
441
        this.toggle(Object.assign({}, this._overlayDefaults, this.overlaySettings, overlaySettings));
92✔
442
    }
443

444
    /** @hidden @internal */
445
    public ngAfterContentInit() {
446
        this._overlayDefaults = {
475✔
447
            target: this.getEditElement(),
448
            modal: false,
449
            positionStrategy: new SelectPositioningStrategy(this),
450
            scrollStrategy: new AbsoluteScrollStrategy(),
451
            excludeFromOutsideClick: [this.inputGroup.element.nativeElement as HTMLElement]
452
        };
453
        const changes$ = this.children.changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
475✔
454
            this.setSelection(this.items.find(x => x.value === this.value));
1,499✔
455
            this.cdr.detectChanges();
291✔
456
        });
457
        Promise.resolve().then(() => {
475✔
458
            if (!changes$.closed) {
475✔
459
                this.children.notifyOnChanges();
244✔
460
            }
461
        });
462
    }
463

464
    /**
465
     * Event handlers
466
     *
467
     * @hidden @internal
468
     */
469
    public handleOpening(e: ToggleViewCancelableEventArgs) {
470
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
160✔
471
        this.opening.emit(args);
160✔
472

473
        e.cancel = args.cancel;
160✔
474
        if (args.cancel) {
160!
475
            return;
×
476
        }
477
    }
478

479
    /** @hidden @internal */
480
    public override onToggleContentAppended(event: ToggleViewEventArgs) {
481
        const info = this.overlayService.getOverlayById(event.id);
159✔
482
        if (info?.settings?.positionStrategy instanceof SelectPositioningStrategy) {
159!
483
            return;
×
484
        }
485
        super.onToggleContentAppended(event);
159✔
486
    }
487

488
    /** @hidden @internal */
489
    public handleOpened() {
490
        this.updateItemFocus();
152✔
491
        this.opened.emit({ owner: this });
152✔
492
    }
493

494
    /** @hidden @internal */
495
    public handleClosing(e: ToggleViewCancelableEventArgs) {
496
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
120✔
497
        this.closing.emit(args);
120✔
498
        e.cancel = args.cancel;
120✔
499
    }
500

501
    /** @hidden @internal */
502
    public handleClosed() {
503
        this.focusItem(false);
119✔
504
        this.closed.emit({ owner: this });
119✔
505
    }
506

507
    /** @hidden @internal */
508
    public onBlur(): void {
509
        this._onTouchedCallback();
89✔
510
        if (this.ngControl && this.ngControl.invalid) {
89✔
511
            this.input.valid = IgxInputState.INVALID;
3✔
512
        } else {
513
            this.input.valid = IgxInputState.INITIAL;
86✔
514
        }
515
    }
516

517
    /** @hidden @internal */
518
    public onFocus(): void {
519
        this._onTouchedCallback();
114✔
520
    }
521

522
    /**
523
     * @hidden @internal
524
     */
525
    public override ngOnInit() {
526
        this.ngControl = this._injector.get<NgControl>(NgControl, null);
476✔
527
        super.ngOnInit();
476✔
528
    }
529

530
    /**
531
     * @hidden @internal
532
     */
533
    public override ngAfterViewInit() {
534
        super.ngAfterViewInit();
475✔
535

536
        if (this.ngControl) {
475✔
537
            this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged.bind(this));
416✔
538
            this.manageRequiredAsterisk();
416✔
539
        }
540
        this.cdr.detectChanges();
475✔
541
    }
542

543
    /** @hidden @internal */
544
    public ngAfterContentChecked() {
545
        if (this.inputGroup && this.prefixes?.length > 0) {
2,567✔
546
            this.inputGroup.prefixes = this.prefixes;
332✔
547
        }
548

549
        if (this.inputGroup && this.suffixes?.length > 0) {
2,567✔
550
            this.inputGroup.suffixes = this.suffixes;
4✔
551
        }
552
    }
553

554
    /**
555
     * @hidden @internal
556
     * Prevent input blur - closing the items container on Header/Footer Template click.
557
     */
558
    public mousedownHandler(event) {
559
        event.preventDefault();
×
560
    }
561

562
    protected onStatusChanged() {
563
        this.manageRequiredAsterisk();
498✔
564
        if (this.ngControl && !this.disabled && this.isTouchedOrDirty) {
498✔
565
            if (this.hasValidators && this.inputGroup.isFocused) {
276✔
566
                this.input.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
3✔
567
            } else {
568
                // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
569
                this.input.valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
273✔
570
            }
571
        } else {
572
            this.input.valid = IgxInputState.INITIAL;
222✔
573
        }
574
    }
575

576
    private get isTouchedOrDirty(): boolean {
577
        return (this.ngControl.control.touched || this.ngControl.control.dirty);
457✔
578
    }
579

580
    private get hasValidators(): boolean {
581
        return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
276✔
582
    }
583

584
    protected override navigate(direction: Navigate, currentIndex?: number) {
585
        if (this.collapsed && this.selectedItem) {
231✔
586
            this.navigateItem(this.selectedItem.itemIndex);
41✔
587
        }
588
        super.navigate(direction, currentIndex);
231✔
589
    }
590

591
    protected manageRequiredAsterisk(): void {
592
        const hasRequiredHTMLAttribute = this.elementRef.nativeElement.hasAttribute('required');
914✔
593
        if (this.ngControl && this.ngControl.control.validator) {
914✔
594
            // Run the validation with empty object to check if required is enabled.
595
            const error = this.ngControl.control.validator({} as AbstractControl);
18✔
596
            this.inputGroup.isRequired = error && error.required;
18✔
597
            this.cdr.markForCheck();
18✔
598

599
            // If validator is dynamically cleared and no required HTML attribute is set,
600
            // reset label's required class(asterisk) and IgxInputState #6896
601
        } else if (this.inputGroup.isRequired && this.ngControl && !this.ngControl.control.validator && !hasRequiredHTMLAttribute) {
896✔
602
            this.input.valid = IgxInputState.INITIAL;
2✔
603
            this.inputGroup.isRequired = false;
2✔
604
            this.cdr.markForCheck();
2✔
605
        }
606
    }
607

608
    private setSelection(item: IgxDropDownItemBaseDirective) {
609
        if (item && item.value !== undefined && item.value !== null) {
1,328✔
610
            this.selection.set(this.id, new Set([item]));
686✔
611
        } else {
612
            this.selection.clear(this.id);
642✔
613
        }
614
    }
615
}
616

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