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

IgniteUI / igniteui-angular / 13416627295

19 Feb 2025 03:46PM CUT coverage: 91.615% (+0.02%) from 91.595%
13416627295

Pull #15246

github

web-flow
Merge 2a114cdda into 10ddb05cf
Pull Request #15246: fix(excel-export): Get correct grid column collection from row island…

12987 of 15218 branches covered (85.34%)

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

380 existing lines in 31 files now uncovered.

26385 of 28800 relevant lines covered (91.61%)

34358.69 hits per line

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

98.09
/projects/igniteui-angular/src/lib/combo/combo.common.ts
1
import {
2
    AfterContentChecked,
3
    AfterViewChecked,
4
    AfterViewInit,
5
    booleanAttribute,
6
    ChangeDetectorRef,
7
    ContentChild,
8
    ContentChildren,
9
    Directive,
10
    ElementRef,
11
    EventEmitter,
12
    forwardRef,
13
    HostBinding,
14
    Inject,
15
    InjectionToken,
16
    Injector,
17
    Input,
18
    OnDestroy,
19
    OnInit,
20
    Optional,
21
    Output,
22
    QueryList,
23
    TemplateRef,
24
    ViewChild
25
} from '@angular/core';
26
import { AbstractControl, ControlValueAccessor, NgControl } from '@angular/forms';
27
import { caseSensitive } from '@igniteui/material-icons-extended';
28
import { noop, Subject } from 'rxjs';
29
import { takeUntil } from 'rxjs/operators';
30
import { IgxSelectionAPIService } from '../core/selection';
31
import { CancelableBrowserEventArgs, cloneArray, IBaseCancelableBrowserEventArgs, IBaseEventArgs, rem } from '../core/utils';
32
import { SortingDirection } from '../data-operations/sorting-strategy';
33
import { IForOfState, IgxForOfDirective } from '../directives/for-of/for_of.directive';
34
import { IgxIconService } from '../icon/icon.service';
35
import { IgxInputGroupType, IGX_INPUT_GROUP_TYPE } from '../input-group/inputGroupType';
36
import { IgxInputDirective, IgxInputGroupComponent, IgxInputState, IgxLabelDirective, IgxPrefixDirective, IgxSuffixDirective } from '../input-group/public_api';
37
import { AbsoluteScrollStrategy, AutoPositionStrategy, OverlaySettings } from '../services/public_api';
38
import { IgxComboDropDownComponent } from './combo-dropdown.component';
39
import { IgxComboAPIService } from './combo.api';
40
import {
41
    IgxComboAddItemDirective, IgxComboClearIconDirective, IgxComboEmptyDirective,
42
    IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboHeaderItemDirective, IgxComboItemDirective, IgxComboToggleIconDirective
43
} from './combo.directives';
44
import { IComboItemAdditionEvent, IComboSearchInputEventArgs } from './public_api';
45
import { ComboResourceStringsEN, IComboResourceStrings } from '../core/i18n/combo-resources';
46
import { getCurrentResourceStrings } from '../core/i18n/resources';
47
import { DOCUMENT } from '@angular/common';
48
import { isEqual } from 'lodash-es';
49

50
export const IGX_COMBO_COMPONENT = /*@__PURE__*/new InjectionToken<IgxComboBase>('IgxComboComponentToken');
2✔
51

52
/** @hidden @internal TODO: Evaluate */
53
export interface IgxComboBase {
54
    id: string;
55
    data: any[] | null;
56
    valueKey: string;
57
    groupKey: string;
58
    isRemote: boolean;
59
    filteredData: any[] | null;
60
    totalItemCount: number;
61
    itemsMaxHeight: number;
62
    itemHeight: number;
63
    searchValue: string;
64
    searchInput: ElementRef<HTMLInputElement>;
65
    comboInput: ElementRef<HTMLInputElement>;
66
    opened: EventEmitter<IBaseEventArgs>;
67
    opening: EventEmitter<CancelableBrowserEventArgs>;
68
    closing: EventEmitter<CancelableBrowserEventArgs>;
69
    closed: EventEmitter<IBaseEventArgs>;
70
    focusSearchInput(opening?: boolean): void;
71
    triggerCheck(): void;
72
    addItemToCollection(): void;
73
    isAddButtonVisible(): boolean;
74
    handleInputChange(event?: string): void;
75
    isItemSelected(itemID: any): boolean;
76
    select(item: any): void;
77
    select(itemIDs: any[], clearSelection?: boolean, event?: Event): void;
78
    deselect(...args: [] | [itemIDs: any[], event?: Event]): void;
79
    setActiveDescendant(): void;
80
}
81

82
let NEXT_ID = 0;
2✔
83

84

85
/** @hidden @internal */
86
export const enum DataTypes {
87
    EMPTY = 'empty',
88
    PRIMITIVE = 'primitive',
89
    COMPLEX = 'complex',
90
    PRIMARYKEY = 'valueKey'
91
}
92

93
/** The filtering criteria to be applied on data search */
94
export interface IComboFilteringOptions {
95
    /** Defines filtering case-sensitivity */
96
    caseSensitive?: boolean;
97
    /**
98
     * Defines whether filtering is allowed
99
     * @deprecated in version 18.2.0. Use the `disableFiltering` property instead.
100
    */
101
    filterable?: boolean;
102
    /** Defines optional key to filter against complex list items. Default to displayKey if provided.*/
103
    filteringKey?: string;
104
}
105

106
@Directive()
107
export abstract class IgxComboBaseDirective implements IgxComboBase, AfterViewChecked, OnInit,
2✔
108
    AfterViewInit, AfterContentChecked, OnDestroy, ControlValueAccessor {
109
    /**
110
     * Defines whether the caseSensitive icon should be shown in the search input
111
     *
112
     * ```typescript
113
     * // get
114
     * let myComboShowSearchCaseIcon = this.combo.showSearchCaseIcon;
115
     * ```
116
     *
117
     * ```html
118
     * <!--set-->
119
     * <igx-combo [showSearchCaseIcon]='true'></igx-combo>
120
     * ```
121
     */
122
    @Input({ transform: booleanAttribute })
123
    public showSearchCaseIcon = false;
257✔
124

125
    /**
126
     * Set custom overlay settings that control how the combo's list of items is displayed.
127
     * Set:
128
     * ```html
129
     * <igx-combo [overlaySettings]="customOverlaySettings"></igx-combo>
130
     * ```
131
     *
132
     * ```typescript
133
     *  const customSettings = { positionStrategy: { settings: { target: myTarget } } };
134
     *  combo.overlaySettings = customSettings;
135
     * ```
136
     * Get any custom overlay settings used by the combo:
137
     * ```typescript
138
     *  const comboOverlaySettings: OverlaySettings = myCombo.overlaySettings;
139
     * ```
140
     */
141
    @Input()
142
    public overlaySettings: OverlaySettings = null;
257✔
143

144
    /**
145
     * Gets/gets combo id.
146
     *
147
     * ```typescript
148
     * // get
149
     * let id = this.combo.id;
150
     * ```
151
     *
152
     * ```html
153
     * <!--set-->
154
     * <igx-combo [id]='combo1'></igx-combo>
155
     * ```
156
     */
157
    @HostBinding('attr.id')
158
    @Input()
159
    public get id(): string {
160
        return this._id;
52,040✔
161
    }
162

163
    public set id(value: string) {
164
        if (!value) {
21!
UNCOV
165
            return;
×
166
        }
167
        const selection = this.selectionService.get(this._id);
21✔
168
        this.selectionService.clear(this._id);
21✔
169
        this._id = value;
21✔
170
        if (selection) {
21✔
171
            this.selectionService.set(this._id, selection);
10✔
172
        }
173
    }
174

175
    /**
176
     * Sets the style width of the element
177
     *
178
     * ```typescript
179
     * // get
180
     * let myComboWidth = this.combo.width;
181
     * ```
182
     *
183
     * ```html
184
     * <!--set-->
185
     * <igx-combo [width]='250px'></igx-combo>
186
     * ```
187
     */
188
    @HostBinding('style.width')
189
    @Input()
190
    public width: string;
191

192
    /**
193
     * Controls whether custom values can be added to the collection
194
     *
195
     * ```typescript
196
     * // get
197
     * let comboAllowsCustomValues = this.combo.allowCustomValues;
198
     * ```
199
     *
200
     * ```html
201
     * <!--set-->
202
     * <igx-combo [allowCustomValues]='true'></igx-combo>
203
     * ```
204
     */
205
    @Input({ transform: booleanAttribute })
206
    public allowCustomValues = false;
257✔
207

208
    /**
209
     * Configures the drop down list height
210
     *
211
     * ```typescript
212
     * // get
213
     * let myComboItemsMaxHeight = this.combo.itemsMaxHeight;
214
     * ```
215
     *
216
     * ```html
217
     * <!--set-->
218
     * <igx-combo [itemsMaxHeight]='320'></igx-combo>
219
     * ```
220
     */
221
    @Input()
222
    public get itemsMaxHeight(): number {
223
        if (this.itemHeight && !this._itemsMaxHeight) {
4,578✔
224
            return this.itemHeight * this.itemsInContainer;
12✔
225
        }
226
        return this._itemsMaxHeight;
4,566✔
227
    }
228

229
    public set itemsMaxHeight(val: number) {
230
        this._itemsMaxHeight = val;
17✔
231
    }
232

233
    /** @hidden */
234
    public get itemsMaxHeightInRem() {
235
        if (this.itemsMaxHeight) {
2,212✔
236
            return rem(this.itemsMaxHeight);
154✔
237
        }
238
    }
239

240
    /**
241
     * Configures the drop down list item height
242
     *
243
     * ```typescript
244
     * // get
245
     * let myComboItemHeight = this.combo.itemHeight;
246
     * ```
247
     *
248
     * ```html
249
     * <!--set-->
250
     * <igx-combo [itemHeight]='32'></igx-combo>
251
     * ```
252
     */
253
    @Input()
254
    public get itemHeight(): number {
255
        return this._itemHeight;
25,141✔
256
    }
257

258
    public set itemHeight(val: number) {
259
        this._itemHeight = val;
17✔
260
    }
261

262
    /**
263
     * Configures the drop down list width
264
     *
265
     * ```typescript
266
     * // get
267
     * let myComboItemsWidth = this.combo.itemsWidth;
268
     * ```
269
     *
270
     * ```html
271
     * <!--set-->
272
     * <igx-combo [itemsWidth] = '"180px"'></igx-combo>
273
     * ```
274
     */
275
    @Input()
276
    public itemsWidth: string;
277

278
    /**
279
     * Defines the placeholder value for the combo value field
280
     *
281
     * ```typescript
282
     * // get
283
     * let myComboPlaceholder = this.combo.placeholder;
284
     * ```
285
     *
286
     * ```html
287
     * <!--set-->
288
     * <igx-combo [placeholder]='newPlaceHolder'></igx-combo>
289
     * ```
290
     */
291
    @Input()
292
    public placeholder: string;
293

294
    /**
295
     * Combo data source.
296
     *
297
     * ```html
298
     * <!--set-->
299
     * <igx-combo [data]='items'></igx-combo>
300
     * ```
301
     */
302
    @Input()
303
    public get data(): any[] | null {
304
        return this._data;
25,051✔
305
    }
306
    public set data(val: any[] | null) {
307
        // igxFor directive ignores undefined values
308
        // if the combo uses simple data and filtering is applied
309
        // an error will occur due to the mismatch of the length of the data
310
        // this can occur during filtering for the igx-combo and
311
        // during filtering & selection for the igx-simple-combo
312
        // since the simple combo's input is both a container for the selection and a filter
313
        this._data = (val) ? val.filter(x => x !== undefined) : [];
9,284✔
314
    }
315

316
    /**
317
     * Determines which column in the data source is used to determine the value.
318
     *
319
     * ```typescript
320
     * // get
321
     * let myComboValueKey = this.combo.valueKey;
322
     * ```
323
     *
324
     * ```html
325
     * <!--set-->
326
     * <igx-combo [valueKey]='myKey'></igx-combo>
327
     * ```
328
     */
329
    @Input()
330
    public valueKey: string = null;
257✔
331

332
    @Input()
333
    public set displayKey(val: string) {
334
        this._displayKey = val;
72✔
335
    }
336

337
    /**
338
     * Determines which column in the data source is used to determine the display value.
339
     *
340
     * ```typescript
341
     * // get
342
     * let myComboDisplayKey = this.combo.displayKey;
343
     *
344
     * // set
345
     * this.combo.displayKey = 'val';
346
     *
347
     * ```
348
     *
349
     * ```html
350
     * <!--set-->
351
     * <igx-combo [displayKey]='myDisplayKey'></igx-combo>
352
     * ```
353
     */
354
    public get displayKey() {
355
        return this._displayKey ? this._displayKey : this.valueKey;
83,800✔
356
    }
357

358
    /**
359
     * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].
360
     *
361
     * ```html
362
     * <!--set-->
363
     * <igx-combo [groupKey]='newGroupKey'></igx-combo>
364
     * ```
365
     */
366
    @Input()
367
    public set groupKey(val: string) {
368
        this._groupKey = val;
158✔
369
    }
370

371
    /**
372
     * The item property by which items should be grouped inside the items list. Not usable if data is not of type Object[].
373
     *
374
     * ```typescript
375
     * // get
376
     * let currentGroupKey = this.combo.groupKey;
377
     * ```
378
     */
379
    public get groupKey(): string {
380
        return this._groupKey;
5,798✔
381
    }
382

383
    /**
384
     * Sets groups sorting order.
385
     *
386
     * @example
387
     * ```html
388
     * <igx-combo [groupSortingDirection]="groupSortingDirection"></igx-combo>
389
     * ```
390
     * ```typescript
391
     * public groupSortingDirection = SortingDirection.Asc;
392
     * ```
393
     */
394
    @Input()
395
    public get groupSortingDirection(): SortingDirection {
396
        return this._groupSortingDirection;
2,212✔
397
    }
398
    public set groupSortingDirection(val: SortingDirection) {
399
        this._groupSortingDirection = val;
6✔
400
    }
401

402
    /**
403
     * Gets/Sets the custom filtering function of the combo.
404
     *
405
     * @example
406
     * ```html
407
     *  <igx-comb #combo [data]="localData" [filterFunction]="filterFunction"></igx-combo>
408
     * ```
409
     */
410
    @Input()
411
    public filterFunction: (collection: any[], searchValue: any, filteringOptions: IComboFilteringOptions) => any[];
412

413
    /**
414
     * Sets aria-labelledby attribute value.
415
     * ```html
416
     * <igx-combo [ariaLabelledBy]="'label1'">
417
     * ```
418
     */
419
    @Input()
420
    public ariaLabelledBy: string;
421

422
    /** @hidden @internal */
423
    @HostBinding('class.igx-combo')
424
    public cssClass = 'igx-combo'; // Independent of display density for the time being
257✔
425

426
    /**
427
     * Disables the combo. The default is `false`.
428
     * ```html
429
     * <igx-combo [disabled]="'true'">
430
     * ```
431
     */
432
    @Input({ transform: booleanAttribute })
433
    public disabled = false;
257✔
434

435
    /**
436
     * Sets the visual combo type.
437
     * The allowed values are `line`, `box`, `border` and `search`. The default is `box`.
438
     * ```html
439
     * <igx-combo [type]="'line'">
440
     * ```
441
     */
442
    @Input()
443
    public get type(): IgxInputGroupType {
444
        return this._type || this._inputGroupType || 'box';
3,417✔
445
    }
446

447
    public set type(val: IgxInputGroupType) {
448
        this._type = val;
4✔
449
    }
450

451
    /**
452
     * Gets/Sets the resource strings.
453
     *
454
     * @remarks
455
     * By default it uses EN resources.
456
     */
457
    @Input()
458
    public get resourceStrings(): IComboResourceStrings {
459
        return this._resourceStrings;
1,955✔
460
    }
461
    public set resourceStrings(value: IComboResourceStrings) {
UNCOV
462
        this._resourceStrings = Object.assign({}, this._resourceStrings, value);
×
463
    }
464

465
    /**
466
     * Emitted before the dropdown is opened
467
     *
468
     * ```html
469
     * <igx-combo opening='handleOpening($event)'></igx-combo>
470
     * ```
471
     */
472
    @Output()
473
    public opening = new EventEmitter<IBaseCancelableBrowserEventArgs>();
257✔
474

475
    /**
476
     * Emitted after the dropdown is opened
477
     *
478
     * ```html
479
     * <igx-combo (opened)='handleOpened($event)'></igx-combo>
480
     * ```
481
     */
482
    @Output()
483
    public opened = new EventEmitter<IBaseEventArgs>();
257✔
484

485
    /**
486
     * Emitted before the dropdown is closed
487
     *
488
     * ```html
489
     * <igx-combo (closing)='handleClosing($event)'></igx-combo>
490
     * ```
491
     */
492
    @Output()
493
    public closing = new EventEmitter<IBaseCancelableBrowserEventArgs>();
257✔
494

495
    /**
496
     * Emitted after the dropdown is closed
497
     *
498
     * ```html
499
     * <igx-combo (closed)='handleClosed($event)'></igx-combo>
500
     * ```
501
     */
502
    @Output()
503
    public closed = new EventEmitter<IBaseEventArgs>();
257✔
504

505
    /**
506
     * Emitted when an item is being added to the data collection
507
     *
508
     * ```html
509
     * <igx-combo (addition)='handleAdditionEvent($event)'></igx-combo>
510
     * ```
511
     */
512
    @Output()
513
    public addition = new EventEmitter<IComboItemAdditionEvent>();
257✔
514

515
    /**
516
     * Emitted when the value of the search input changes (e.g. typing, pasting, clear, etc.)
517
     *
518
     * ```html
519
     * <igx-combo (searchInputUpdate)='handleSearchInputEvent($event)'></igx-combo>
520
     * ```
521
     */
522
    @Output()
523
    public searchInputUpdate = new EventEmitter<IComboSearchInputEventArgs>();
257✔
524

525
    /**
526
     * Emitted when new chunk of data is loaded from the virtualization
527
     *
528
     * ```html
529
     * <igx-combo (dataPreLoad)='handleDataPreloadEvent($event)'></igx-combo>
530
     * ```
531
     */
532
    @Output()
533
    public dataPreLoad = new EventEmitter<IForOfState>();
257✔
534

535
    /**
536
     * The custom template, if any, that should be used when rendering ITEMS in the combo list
537
     *
538
     * ```typescript
539
     * // Set in typescript
540
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
541
     * myComponent.combo.itemTemplate = myCustomTemplate;
542
     * ```
543
     * ```html
544
     * <!-- Set in markup -->
545
     *  <igx-combo #combo>
546
     *      ...
547
     *      <ng-template igxComboItem let-item let-key="valueKey">
548
     *          <div class="custom-item">
549
     *              <div class="custom-item__name">{{ item[key] }}</div>
550
     *              <div class="custom-item__cost">{{ item.cost }}</div>
551
     *          </div>
552
     *      </ng-template>
553
     *  </igx-combo>
554
     * ```
555
     */
556
    @ContentChild(IgxComboItemDirective, { read: TemplateRef })
557
    public itemTemplate: TemplateRef<any> = null;
257✔
558

559
    /**
560
     * The custom template, if any, that should be used when rendering the HEADER for the combo items list
561
     *
562
     * ```typescript
563
     * // Set in typescript
564
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
565
     * myComponent.combo.headerTemplate = myCustomTemplate;
566
     * ```
567
     * ```html
568
     * <!-- Set in markup -->
569
     *  <igx-combo #combo>
570
     *      ...
571
     *      <ng-template igxComboHeader>
572
     *          <div class="combo__header">
573
     *              This is a custom header
574
     *          </div>
575
     *      </ng-template>
576
     *  </igx-combo>
577
     * ```
578
     */
579
    @ContentChild(IgxComboHeaderDirective, { read: TemplateRef })
580
    public headerTemplate: TemplateRef<any> = null;
257✔
581

582
    /**
583
     * The custom template, if any, that should be used when rendering the FOOTER for the combo items list
584
     *
585
     * ```typescript
586
     * // Set in typescript
587
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
588
     * myComponent.combo.footerTemplate = myCustomTemplate;
589
     * ```
590
     * ```html
591
     * <!-- Set in markup -->
592
     *  <igx-combo #combo>
593
     *      ...
594
     *      <ng-template igxComboFooter>
595
     *          <div class="combo__footer">
596
     *              This is a custom footer
597
     *          </div>
598
     *      </ng-template>
599
     *  </igx-combo>
600
     * ```
601
     */
602
    @ContentChild(IgxComboFooterDirective, { read: TemplateRef })
603
    public footerTemplate: TemplateRef<any> = null;
257✔
604

605
    /**
606
     * The custom template, if any, that should be used when rendering HEADER ITEMS for groups in the combo list
607
     *
608
     * ```typescript
609
     * // Set in typescript
610
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
611
     * myComponent.combo.headerItemTemplate = myCustomTemplate;
612
     * ```
613
     * ```html
614
     * <!-- Set in markup -->
615
     *  <igx-combo #combo>
616
     *      ...
617
     *      <ng-template igxComboHeaderItem let-item let-key="groupKey">
618
     *          <div class="custom-item--group">Group header for {{ item[key] }}</div>
619
     *      </ng-template>
620
     *  </igx-combo>
621
     * ```
622
     */
623
    @ContentChild(IgxComboHeaderItemDirective, { read: TemplateRef })
624
    public headerItemTemplate: TemplateRef<any> = null;
257✔
625

626
    /**
627
     * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down
628
     *
629
     * ```typescript
630
     * // Set in typescript
631
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
632
     * myComponent.combo.addItemTemplate = myCustomTemplate;
633
     * ```
634
     * ```html
635
     * <!-- Set in markup -->
636
     *  <igx-combo #combo>
637
     *      ...
638
     *      <ng-template igxComboAddItem>
639
     *          <button type="button" igxButton="contained" class="combo__add-button">
640
     *              Click to add item
641
     *          </button>
642
     *      </ng-template>
643
     *  </igx-combo>
644
     * ```
645
     */
646
    @ContentChild(IgxComboAddItemDirective, { read: TemplateRef })
647
    public addItemTemplate: TemplateRef<any> = null;
257✔
648

649
    /**
650
     * The custom template, if any, that should be used when rendering the ADD BUTTON in the combo drop down
651
     *
652
     * ```typescript
653
     * // Set in typescript
654
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
655
     * myComponent.combo.emptyTemplate = myCustomTemplate;
656
     * ```
657
     * ```html
658
     * <!-- Set in markup -->
659
     *  <igx-combo #combo>
660
     *      ...
661
     *      <ng-template igxComboEmpty>
662
     *          <div class="combo--empty">
663
     *              There are no items to display
664
     *          </div>
665
     *      </ng-template>
666
     *  </igx-combo>
667
     * ```
668
     */
669
    @ContentChild(IgxComboEmptyDirective, { read: TemplateRef })
670
    public emptyTemplate: TemplateRef<any> = null;
257✔
671

672
    /**
673
     * The custom template, if any, that should be used when rendering the combo TOGGLE(open/close) button
674
     *
675
     * ```typescript
676
     * // Set in typescript
677
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
678
     * myComponent.combo.toggleIconTemplate = myCustomTemplate;
679
     * ```
680
     * ```html
681
     * <!-- Set in markup -->
682
     *  <igx-combo #combo>
683
     *      ...
684
     *      <ng-template igxComboToggleIcon let-collapsed>
685
     *          <igx-icon>{{ collapsed ? 'remove_circle' : 'remove_circle_outline'}}</igx-icon>
686
     *      </ng-template>
687
     *  </igx-combo>
688
     * ```
689
     */
690
    @ContentChild(IgxComboToggleIconDirective, { read: TemplateRef })
691
    public toggleIconTemplate: TemplateRef<any> = null;
257✔
692

693
    /**
694
     * The custom template, if any, that should be used when rendering the combo CLEAR button
695
     *
696
     * ```typescript
697
     * // Set in typescript
698
     * const myCustomTemplate: TemplateRef<any> = myComponent.customTemplate;
699
     * myComponent.combo.clearIconTemplate = myCustomTemplate;
700
     * ```
701
     * ```html
702
     * <!-- Set in markup -->
703
     *  <igx-combo #combo>
704
     *      ...
705
     *      <ng-template igxComboClearIcon>
706
     *          <igx-icon>clear</igx-icon>
707
     *      </ng-template>
708
     *  </igx-combo>
709
     * ```
710
     */
711
    @ContentChild(IgxComboClearIconDirective, { read: TemplateRef })
712
    public clearIconTemplate: TemplateRef<any> = null;
257✔
713

714
    /** @hidden @internal */
715
    @ContentChild(forwardRef(() => IgxLabelDirective), { static: true }) public label: IgxLabelDirective;
2✔
716

717
    /** @hidden @internal */
718
    @ViewChild('inputGroup', { read: IgxInputGroupComponent, static: true })
719
    public inputGroup: IgxInputGroupComponent;
720

721
    /** @hidden @internal */
722
    @ViewChild('comboInput', { read: IgxInputDirective, static: true })
723
    public comboInput: IgxInputDirective;
724

725
    /** @hidden @internal */
726
    @ViewChild('searchInput')
727
    public searchInput: ElementRef<HTMLInputElement> = null;
257✔
728

729
    /** @hidden @internal */
730
    @ViewChild(IgxForOfDirective, { static: true })
731
    public virtualScrollContainer: IgxForOfDirective<any>;
732

733
    @ViewChild(IgxForOfDirective, { read: IgxForOfDirective, static: true })
734
    protected virtDir: IgxForOfDirective<any>;
735

736
    @ViewChild('dropdownItemContainer', { static: true })
737
    protected dropdownContainer: ElementRef = null;
257✔
738

739
    @ViewChild('primitive', { read: TemplateRef, static: true })
740
    protected primitiveTemplate: TemplateRef<any>;
741

742
    @ViewChild('complex', { read: TemplateRef, static: true })
743
    protected complexTemplate: TemplateRef<any>;
744

745
    @ContentChildren(IgxPrefixDirective, { descendants: true })
746
    protected prefixes: QueryList<IgxPrefixDirective>;
747

748
    @ContentChildren(IgxSuffixDirective, { descendants: true })
749
    protected suffixes: QueryList<IgxSuffixDirective>;
750

751
    /** @hidden @internal */
752
    public get searchValue(): string {
753
        return this._searchValue;
13,014✔
754
    }
755
    public set searchValue(val: string) {
756
        this.filterValue = val;
73✔
757
        this._searchValue = val;
73✔
758
    }
759

760
    /** @hidden @internal */
761
    public get isRemote() {
762
        return this.totalItemCount > 0 &&
694✔
763
            this.valueKey &&
764
            this.dataType === DataTypes.COMPLEX;
765
    }
766

767
    /** @hidden @internal */
768
    public get dataType(): string {
769
        if (this.displayKey) {
15,466✔
770
            return DataTypes.COMPLEX;
13,631✔
771
        }
772
        return DataTypes.PRIMITIVE;
1,835✔
773
    }
774

775
    /**
776
     * Gets if control is valid, when used in a form
777
     *
778
     * ```typescript
779
     * // get
780
     * let valid = this.combo.valid;
781
     * ```
782
     */
783
    public get valid(): IgxInputState {
784
        return this._valid;
60✔
785
    }
786

787
    /**
788
     * Sets if control is valid, when used in a form
789
     *
790
     * ```typescript
791
     * // set
792
     * this.combo.valid = IgxInputState.INVALID;
793
     * ```
794
     */
795
    public set valid(valid: IgxInputState) {
796
        this._valid = valid;
131✔
797
        this.comboInput.valid = valid;
131✔
798
    }
799

800
    /**
801
     * The value of the combo
802
     *
803
     * ```typescript
804
     * // get
805
     * let comboValue = this.combo.value;
806
     * ```
807
     */
808
    public get value(): any[] {
809
        return this._value;
52✔
810
    }
811

812
    /**
813
     * The text displayed in the combo input
814
     *
815
     * ```typescript
816
     * // get
817
     * let comboDisplayValue = this.combo.displayValue;
818
     * ```
819
     */
820
    public get displayValue(): string {
821
        return this._displayValue;
3,517✔
822
    }
823

824
    /**
825
     * Defines the current state of the virtualized data. It contains `startIndex` and `chunkSize`
826
     *
827
     * ```typescript
828
     * // get
829
     * let state = this.combo.virtualizationState;
830
     * ```
831
     */
832
    public get virtualizationState(): IForOfState {
833
        return this.virtDir.state;
21✔
834
    }
835
    /**
836
     * Sets the current state of the virtualized data.
837
     *
838
     * ```typescript
839
     * // set
840
     * this.combo.virtualizationState(state);
841
     * ```
842
     */
843
    public set virtualizationState(state: IForOfState) {
UNCOV
844
        this.virtDir.state = state;
×
845
    }
846

847
    /**
848
     * Gets drop down state.
849
     *
850
     * ```typescript
851
     * let state = this.combo.collapsed;
852
     * ```
853
     */
854
    public get collapsed(): boolean {
855
        return this.dropdown.collapsed;
572✔
856
    }
857

858
    /**
859
     * Gets total count of the virtual data items, when using remote service.
860
     *
861
     * ```typescript
862
     * // get
863
     * let count = this.combo.totalItemCount;
864
     * ```
865
     */
866
    public get totalItemCount(): number {
867
        return this.virtDir.totalItemCount;
645✔
868
    }
869
    /**
870
     * Sets total count of the virtual data items, when using remote service.
871
     *
872
     * ```typescript
873
     * // set
874
     * this.combo.totalItemCount(remoteService.count);
875
     * ```
876
     */
877
    public set totalItemCount(count: number) {
878
        this.virtDir.totalItemCount = count;
13✔
879
    }
880

881
    /** @hidden @internal */
882
    public get template(): TemplateRef<any> {
883
        this._dataType = this.dataType;
15,390✔
884
        if (this.itemTemplate) {
15,390✔
885
            return this.itemTemplate;
9,934✔
886
        }
887
        if (this._dataType === DataTypes.COMPLEX) {
5,456✔
888
            return this.complexTemplate;
3,696✔
889
        }
890
        return this.primitiveTemplate;
1,760✔
891
    }
892

893
    /** @hidden @internal */
894
    public customValueFlag = true;
257✔
895
    /** @hidden @internal */
896
    public filterValue = '';
257✔
897
    /** @hidden @internal */
898
    public defaultFallbackGroup = 'Other';
257✔
899
    /** @hidden @internal */
900
    public activeDescendant = '';
257✔
901

902
    /**
903
     * Configures the way combo items will be filtered.
904
     *
905
     * ```typescript
906
     * // get
907
     * let myFilteringOptions = this.combo.filteringOptions;
908
     * ```
909
     *
910
     * ```html
911
     * <!--set-->
912
     * <igx-combo [filteringOptions]='myFilteringOptions'></igx-combo>
913
     * ```
914
     */
915

916
    @Input()
917
    public get filteringOptions(): IComboFilteringOptions {
918
        return this._filteringOptions || this._defaultFilteringOptions;
7,449✔
919
    }
920
    public set filteringOptions(value: IComboFilteringOptions) {
921
        this._filteringOptions = value;
7✔
922
    }
923

924
    protected containerSize = undefined;
257✔
925
    protected itemSize = undefined;
257✔
926
    protected _data = [];
257✔
927
    protected _value = [];
257✔
928
    protected _displayValue = '';
257✔
929
    protected _groupKey = '';
257✔
930
    protected _searchValue = '';
257✔
931
    protected _filteredData = [];
257✔
932
    protected _displayKey: string;
933
    protected _remoteSelection = {};
257✔
934
    protected _resourceStrings = getCurrentResourceStrings(ComboResourceStringsEN);
257✔
935
    protected _valid = IgxInputState.INITIAL;
257✔
936
    protected ngControl: NgControl = null;
257✔
937
    protected destroy$ = new Subject<void>();
257✔
938
    protected _onTouchedCallback: () => void = noop;
257✔
939
    protected _onChangeCallback: (_: any) => void = noop;
257✔
940
    protected compareCollator = new Intl.Collator();
257✔
941
    protected computedStyles;
942

943
    private _id: string = `igx-combo-${NEXT_ID++}`;
257✔
944
    private _type = null;
257✔
945
    private _dataType = '';
257✔
946
    private _itemHeight = undefined;
257✔
947
    private _itemsMaxHeight = null;
257✔
948
    private _overlaySettings: OverlaySettings;
949
    private _groupSortingDirection: SortingDirection = SortingDirection.Asc;
257✔
950
    private _filteringOptions: IComboFilteringOptions;
951
    private _defaultFilteringOptions: IComboFilteringOptions = { caseSensitive: false };
257✔
952
    private itemsInContainer = 10;
257✔
953

954
    public abstract dropdown: IgxComboDropDownComponent;
955
    public abstract selectionChanging: EventEmitter<any>;
956

957
    constructor(
958
        protected elementRef: ElementRef,
257✔
959
        protected cdr: ChangeDetectorRef,
257✔
960
        protected selectionService: IgxSelectionAPIService,
257✔
961
        protected comboAPI: IgxComboAPIService,
257✔
962
        @Inject(DOCUMENT) public document: Document,
257✔
963
        @Optional() @Inject(IGX_INPUT_GROUP_TYPE) protected _inputGroupType: IgxInputGroupType,
257✔
964
        @Optional() protected _injector: Injector,
257✔
965
        @Optional() @Inject(IgxIconService) protected _iconService?: IgxIconService,
257✔
966
    ) { }
967

968
    public ngAfterViewChecked() {
969
        const targetElement = this.inputGroup.element.nativeElement.querySelector('.igx-input-group__bundle') as HTMLElement;
901✔
970

971
        this._overlaySettings = {
901✔
972
            target: targetElement,
973
            scrollStrategy: new AbsoluteScrollStrategy(),
974
            positionStrategy: new AutoPositionStrategy(),
975
            modal: false,
976
            closeOnOutsideClick: true,
977
            excludeFromOutsideClick: [targetElement]
978
        };
979
    }
980

981
    /** @hidden @internal */
982
    public ngAfterContentChecked(): void {
983
        if (this.inputGroup && this.prefixes?.length > 0) {
901!
UNCOV
984
            this.inputGroup.prefixes = this.prefixes;
×
985
        }
986

987
        if (this.inputGroup && this.suffixes?.length > 0) {
901!
UNCOV
988
            this.inputGroup.suffixes = this.suffixes;
×
989
        }
990
    }
991

992
    /** @hidden @internal */
993
    public ngOnInit() {
994

995
        this.ngControl = this._injector.get<NgControl>(NgControl, null);
254✔
996
        this.selectionService.set(this.id, new Set());
254✔
997
        this._iconService?.addSvgIconFromText(caseSensitive.name, caseSensitive.value, 'imx-icons');
254✔
998
        this.computedStyles = this.document.defaultView.getComputedStyle(this.elementRef.nativeElement);
254✔
999
    }
1000

1001
    /** @hidden @internal */
1002
    public ngAfterViewInit(): void {
1003
        this.filteredData = [...this.data];
219✔
1004
        if (this.ngControl) {
219✔
1005
            this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged);
57✔
1006
            this.manageRequiredAsterisk();
57✔
1007
            this.cdr.detectChanges();
57✔
1008
        }
1009
        this.virtDir.chunkPreload.pipe(takeUntil(this.destroy$)).subscribe((e: IForOfState) => {
219✔
1010
            const eventArgs: IForOfState = Object.assign({}, e, { owner: this });
25✔
1011
            this.dataPreLoad.emit(eventArgs);
25✔
1012
        });
1013
        this.dropdown?.opening.subscribe((_args: IBaseCancelableBrowserEventArgs) => {
219✔
1014
            // calculate the container size and item size based on the sizes from the DOM
1015
            const dropdownContainerHeight = this.dropdownContainer.nativeElement.getBoundingClientRect().height;
148✔
1016
            if (dropdownContainerHeight) {
148✔
1017
                this.containerSize = parseFloat(dropdownContainerHeight);
145✔
1018
            }
1019
            if (this.dropdown.children?.first) {
148✔
1020
                this.itemSize = this.dropdown.children.first.element.nativeElement.getBoundingClientRect().height;
146✔
1021
            }
1022
        });
1023
    }
1024

1025
    /** @hidden @internal */
1026
    public ngOnDestroy(): void {
1027
        this.destroy$.next();
221✔
1028
        this.destroy$.complete();
221✔
1029
        this.comboAPI.clear();
221✔
1030
        this.selectionService.delete(this.id);
221✔
1031
    }
1032

1033
    /**
1034
     * A method that opens/closes the combo.
1035
     *
1036
     * ```html
1037
     * <button type="button" (click)="combo.toggle()">Toggle Combo</button>
1038
     * <igx-combo #combo></igx-combo>
1039
     * ```
1040
     */
1041
    public toggle(): void {
1042
        if (this.collapsed && this._displayValue.length !== 0) {
97✔
1043
            this.filterValue = '';
4✔
1044
            this.cdr.detectChanges();
4✔
1045
        }
1046
        const overlaySettings = Object.assign({}, this._overlaySettings, this.overlaySettings);
97✔
1047
        this.dropdown.toggle(overlaySettings);
97✔
1048
        if (!this.collapsed) {
97✔
1049
            this.setActiveDescendant();
95✔
1050
        }
1051
    }
1052

1053
    /**
1054
     * A method that opens the combo.
1055
     *
1056
     * ```html
1057
     * <button type="button" (click)="combo.open()">Open Combo</button>
1058
     * <igx-combo #combo></igx-combo>
1059
     * ```
1060
     */
1061
    public open(): void {
1062
        if (this.collapsed && this._displayValue.length !== 0) {
75✔
1063
            this.filterValue = '';
6✔
1064
            this.cdr.detectChanges();
6✔
1065
        }
1066
        const overlaySettings = Object.assign({}, this._overlaySettings, this.overlaySettings);
75✔
1067
        this.dropdown.open(overlaySettings);
75✔
1068
        this.setActiveDescendant();
75✔
1069
    }
1070

1071
    /**
1072
     * A method that closes the combo.
1073
     *
1074
     * ```html
1075
     * <button type="button" (click)="combo.close()">Close Combo</button>
1076
     * <igx-combo #combo></igx-combo>
1077
     * ```
1078
     */
1079
    public close(): void {
1080
        this.dropdown.close();
63✔
1081
    }
1082

1083
    /**
1084
     * Triggers change detection on the combo view
1085
     */
1086
    public triggerCheck() {
1087
        this.cdr.detectChanges();
67✔
1088
    }
1089

1090
    /**
1091
     * Get current selection state
1092
     *
1093
     * @returns Array of selected items
1094
     * ```typescript
1095
     * let mySelection = this.combo.selection;
1096
     * ```
1097
     */
1098
    public get selection(): any[] {
1099
        const items = Array.from(this.selectionService.get(this.id));
2,109✔
1100
        return this.convertKeysToItems(items);
2,105✔
1101
    }
1102

1103
    /**
1104
     * Returns if the specified itemID is selected
1105
     *
1106
     * @hidden
1107
     * @internal
1108
     */
1109
    public isItemSelected(item: any): boolean {
1110
        return this.selectionService.is_item_selected(this.id, item);
45,725✔
1111
    }
1112

1113
    /** @hidden @internal */
1114
    public get toggleIcon(): string {
1115
        return this.dropdown.collapsed ? 'input_expand' : 'input_collapse';
2,203✔
1116
    }
1117

1118
    /** @hidden @internal */
1119
    public addItemToCollection() {
1120
        if (!this.searchValue) {
17✔
1121
            return;
2✔
1122
        }
1123
        const addedItem = this.displayKey ? {
15✔
1124
            [this.valueKey]: this.searchValue,
1125
            [this.displayKey]: this.searchValue
1126
        } : this.searchValue;
1127
        if (this.groupKey) {
15✔
1128
            Object.assign(addedItem, { [this.groupKey]: this.defaultFallbackGroup });
11✔
1129
        }
1130
        // expose shallow copy instead of this.data in event args so this.data can't be mutated
1131
        const oldCollection = [...this.data];
15✔
1132
        const newCollection = [...this.data, addedItem];
15✔
1133
        const args: IComboItemAdditionEvent = {
15✔
1134
            oldCollection, addedItem, newCollection, owner: this, cancel: false
1135
        };
1136
        this.addition.emit(args);
15✔
1137
        if (args.cancel) {
15✔
1138
            return;
1✔
1139
        }
1140
        this.data.push(args.addedItem);
14✔
1141
        // trigger re-render
1142
        this.data = cloneArray(this.data);
14✔
1143
        this.select(this.valueKey !== null && this.valueKey !== undefined ?
14✔
1144
            [args.addedItem[this.valueKey]] : [args.addedItem], false);
1145
        this.customValueFlag = false;
14✔
1146
        this.searchInput?.nativeElement.focus();
14✔
1147
        this.dropdown.focusedItem = null;
14✔
1148
        this.virtDir.scrollTo(0);
14✔
1149
    }
1150

1151
    /** @hidden @internal */
1152
    public isAddButtonVisible(): boolean {
1153
        // This should always return a boolean value. If this.searchValue was '', it returns '' instead of false;
1154
        return this.searchValue !== '' && this.customValueFlag;
2,236✔
1155
    }
1156

1157
    /** @hidden @internal */
1158
    public handleInputChange(event?: any) {
1159
        if (event !== undefined) {
121✔
1160
            const args: IComboSearchInputEventArgs = {
102✔
1161
                searchText: typeof event === 'string' ? event : event.target.value,
102✔
1162
                owner: this,
1163
                cancel: false
1164
            };
1165
            this.searchInputUpdate.emit(args);
102✔
1166
            if (args.cancel) {
102✔
1167
                this.filterValue = null;
3✔
1168
            }
1169
        }
1170
        this.checkMatch();
121✔
1171
    }
1172

1173
    /**
1174
     * Event handlers
1175
     *
1176
     * @hidden
1177
     * @internal
1178
     */
1179
    public handleOpening(e: IBaseCancelableBrowserEventArgs) {
1180
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
152✔
1181
        this.opening.emit(args);
152✔
1182
        e.cancel = args.cancel;
152✔
1183
    }
1184

1185
    /** @hidden @internal */
1186
    public handleClosing(e: IBaseCancelableBrowserEventArgs) {
1187
        const args: IBaseCancelableBrowserEventArgs = { owner: this, event: e.event, cancel: e.cancel };
21✔
1188
        this.closing.emit(args);
21✔
1189
        e.cancel = args.cancel;
21✔
1190
        if (e.cancel) {
21✔
1191
            return;
1✔
1192
        }
1193
        this.searchValue = '';
20✔
1194
        if (!e.event) {
20✔
1195
            this.comboInput?.nativeElement.focus();
17✔
1196
        } else {
1197
            this._onTouchedCallback();
3✔
1198
            this.updateValidity();
3✔
1199
        }
1200
    }
1201

1202
    /** @hidden @internal */
1203
    public handleClosed() {
1204
        this.closed.emit({ owner: this });
28✔
1205
    }
1206

1207
    /** @hidden @internal */
1208
    public handleKeyDown(event: KeyboardEvent) {
1209
        if (event.key === 'ArrowUp' || event.key === 'Up') {
76✔
1210
            event.preventDefault();
5✔
1211
            event.stopPropagation();
5✔
1212
            this.close();
5✔
1213
        }
1214
    }
1215

1216
    /** @hidden @internal */
1217
    public registerOnChange(fn: any): void {
1218
        this._onChangeCallback = fn;
75✔
1219
    }
1220

1221
    /** @hidden @internal */
1222
    public registerOnTouched(fn: any): void {
1223
        this._onTouchedCallback = fn;
75✔
1224
    }
1225

1226
    /** @hidden @internal */
1227
    public setDisabledState(isDisabled: boolean): void {
1228
        this.disabled = isDisabled;
61✔
1229
    }
1230

1231
    /** @hidden @internal */
1232
    public onClick(event: Event) {
1233
        event.stopPropagation();
11✔
1234
        event.preventDefault();
11✔
1235
        if (!this.disabled) {
11✔
1236
            this.toggle();
9✔
1237
        }
1238
    }
1239

1240
    /** @hidden @internal */
1241
    public onBlur() {
1242
        if (this.collapsed) {
28✔
1243
            this._onTouchedCallback();
17✔
1244
            this.updateValidity();
17✔
1245
        }
1246
    }
1247

1248
    /** @hidden @internal */
1249
    public setActiveDescendant(): void {
1250
        this.activeDescendant = this.dropdown.focusedItem?.id || '';
311✔
1251
    }
1252

1253
    /** @hidden @internal */
1254
    public toggleCaseSensitive() {
1255
        this.filteringOptions = Object.assign({}, this.filteringOptions, { caseSensitive: !this.filteringOptions.caseSensitive });
1✔
1256
    }
1257

1258
    protected onStatusChanged = () => {
257✔
1259
        if (this.ngControl && this.isTouchedOrDirty && !this.disabled) {
112✔
1260
            if (this.hasValidators && (!this.collapsed || this.inputGroup.isFocused)) {
47✔
1261
                this.valid = this.ngControl.valid ? IgxInputState.VALID : IgxInputState.INVALID;
20✔
1262
            } else {
1263
                this.valid = this.ngControl.valid ? IgxInputState.INITIAL : IgxInputState.INVALID;
27✔
1264
            }
1265
        } else {
1266
            // B.P. 18 May 2021: IgxDatePicker does not reset its state upon resetForm #9526
1267
            this.valid = IgxInputState.INITIAL;
65✔
1268
        }
1269
        this.manageRequiredAsterisk();
112✔
1270
    };
1271

1272
    private updateValidity() {
1273
        if (this.ngControl && this.ngControl.invalid) {
20✔
1274
            this.valid = IgxInputState.INVALID;
11✔
1275
        } else {
1276
            this.valid = IgxInputState.INITIAL;
9✔
1277
        }
1278
    }
1279

1280
    private get isTouchedOrDirty(): boolean {
1281
        return (this.ngControl.control.touched || this.ngControl.control.dirty);
112✔
1282
    }
1283

1284
    private get hasValidators(): boolean {
1285
        return (!!this.ngControl.control.validator || !!this.ngControl.control.asyncValidator);
47✔
1286
    }
1287

1288
    /** if there is a valueKey - map the keys to data items, else - just return the keys */
1289
    protected convertKeysToItems(keys: any[]) {
1290
        if (this.valueKey === null || this.valueKey === undefined) {
2,901✔
1291
            return keys;
788✔
1292
        }
1293

1294
        return keys.map(key => {
2,113✔
1295
            const item = this.data.find(entry => isEqual(entry[this.valueKey], key));
55,955✔
1296

1297
            return item !== undefined ? item : { [this.valueKey]: key };
2,853✔
1298
        });
1299
    }
1300

1301
    protected checkMatch(): void {
1302
        const itemMatch = this.filteredData.some(this.findMatch);
710✔
1303
        this.customValueFlag = this.allowCustomValues && !itemMatch;
710✔
1304
    }
1305

1306
    protected findMatch = (element: any): boolean => {
257✔
1307
        const value = this.displayKey ? element[this.displayKey] : element;
21,418✔
1308
        const searchValue = this.searchValue || this.comboInput?.value;
21,418✔
1309
        return value?.toString().trim().toLowerCase() === searchValue.trim().toLowerCase();
21,418✔
1310
    };
1311

1312
    protected manageRequiredAsterisk(): void {
1313
        if (this.ngControl) {
169✔
1314
            this.inputGroup.isRequired = this.required;
169✔
1315
        }
1316
    }
1317

1318
    /** Contains key-value pairs of the selected valueKeys and their resp. displayKeys */
1319
    protected registerRemoteEntries(ids: any[], add = true) {
33✔
1320
        if (add) {
75✔
1321
            const selection = this.getValueDisplayPairs(ids);
33✔
1322
            for (const entry of selection) {
33✔
1323
                this._remoteSelection[entry[this.valueKey]] = entry[this.displayKey];
45✔
1324
            }
1325
        } else {
1326
            for (const entry of ids) {
42✔
1327
                delete this._remoteSelection[entry];
27✔
1328
            }
1329
        }
1330
    }
1331

1332
    /**
1333
     * For `id: any[]` returns a mapped `{ [combo.valueKey]: any, [combo.displayKey]: any }[]`
1334
     */
1335
    protected getValueDisplayPairs(ids: any[]) {
1336
        return this.data.filter(entry => ids.indexOf(entry[this.valueKey]) > -1).map(e => ({
816✔
1337
            [this.valueKey]: e[this.valueKey],
1338
            [this.displayKey]: e[this.displayKey]
1339
        }));
1340
    }
1341

1342
    protected getRemoteSelection(newSelection: any[], oldSelection: any[]): string {
1343
        if (!newSelection.length) {
41✔
1344
            // If new selection is empty, clear all items
1345
            this.registerRemoteEntries(oldSelection, false);
8✔
1346
            return '';
8✔
1347
        }
1348
        const removedItems = oldSelection.filter(e => newSelection.indexOf(e) < 0);
33✔
1349
        const addedItems = newSelection.filter(e => oldSelection.indexOf(e) < 0);
65✔
1350
        this.registerRemoteEntries(addedItems);
33✔
1351
        this.registerRemoteEntries(removedItems, false);
33✔
1352
        return Object.keys(this._remoteSelection).map(e => this._remoteSelection[e]).join(', ');
63✔
1353
    }
1354

1355
    protected get required(): boolean {
1356
        if (this.ngControl && this.ngControl.control && this.ngControl.control.validator) {
456✔
1357
            // Run the validation with empty object to check if required is enabled.
1358
            const error = this.ngControl.control.validator({} as AbstractControl);
115✔
1359
            return error && error.required;
115✔
1360
        }
1361

1362
        return false;
341✔
1363
    }
1364

1365
    public abstract get filteredData(): any[] | null;
1366
    public abstract set filteredData(val: any[] | null);
1367

1368
    public abstract handleOpened();
1369
    public abstract onArrowDown(event: Event);
1370
    public abstract focusSearchInput(opening?: boolean);
1371

1372
    public abstract select(newItem: any): void;
1373
    public abstract select(newItems: Array<any> | any, clearCurrentSelection?: boolean, event?: Event): void;
1374

1375
    public abstract deselect(...args: [] | [items: Array<any>, event?: Event]): void;
1376

1377
    public abstract writeValue(value: any): void;
1378

1379
    protected abstract setSelection(newSelection: Set<any>, event?: Event): void;
1380
    protected abstract createDisplayText(newSelection: any[], oldSelection: any[]);
1381
}
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