Angular ng-select - All in One UI Select, Multiselect and Autocomplete library providing comprehensive select component functionality
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
The NgSelectComponent is the main component providing comprehensive select functionality including single-select, multi-select, autocomplete, virtual scrolling, and extensive customization options.
/**
* Function type for comparing items for selection state
*/
export type CompareWithFn = (a: any, b: any) => boolean;
/**
* Function type for adding custom tags
*/
export type AddTagFn = (term: string) => any | Promise<any>;
/**
* Function type for generating group values from grouped items
*/
export type GroupValueFn = (key: string | any, children: any[]) => string | any;
/**
* Dropdown positioning options
*/
export type DropdownPosition = 'top' | 'right' | 'bottom' | 'left' | 'auto';Main select component implementing Angular's ControlValueAccessor for seamless form integration.
/**
* Main select component with support for single/multi-select, autocomplete, virtual scrolling
* Implements ControlValueAccessor for reactive and template-driven forms integration
*/
@Component({
selector: 'ng-select',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgSelectComponent),
multi: true
}
]
})
export class NgSelectComponent implements ControlValueAccessor, OnInit, OnDestroy, OnChanges, AfterViewInit {
// Data binding properties
/** Array of items to display in dropdown */
@Input() items: any[];
/** Property name to use for display labels (e.g., 'name') */
@Input() bindLabel: string;
/** Property name to use for values (e.g., 'id') */
@Input() bindValue: string;
/** Function to compare items for selection state */
@Input() compareWith: CompareWithFn;
// Selection mode properties
/** Enable multiple selection mode */
@Input() multiple: boolean = false;
/** Enable adding custom tags/items */
@Input() addTag: boolean | AddTagFn = false;
/** Maximum number of items that can be selected */
@Input() maxSelectedItems: number;
// Display properties
/** Placeholder text when no selection */
@Input() placeholder: string = '';
/** Keep placeholder visible even when item is selected */
@Input() fixedPlaceholder: boolean = false;
/** Text shown when no matching results found */
@Input() notFoundText: string = 'No items found';
/** Text shown when search term is required */
@Input() typeToSearchText: string = 'Type to search';
/** Text shown when adding new tags */
@Input() addTagText: string = 'Add item';
/** Text shown during loading state */
@Input() loadingText: string = 'Loading...';
/** Text shown for clear all action */
@Input() clearAllText: string = 'Clear all';
/** Visual appearance style */
@Input() appearance: string;
// Behavior properties
/** Enable search/filter functionality */
@Input() searchable: boolean = true;
/** Show clear button when item is selected */
@Input() clearable: boolean = true;
/** Component is in loading state */
@Input() loading: boolean = false;
/** Component is disabled */
@Input() disabled: boolean = false;
/** Component is readonly */
@Input() readonly: boolean = false;
/** Close dropdown after making selection */
@Input() closeOnSelect: boolean = true;
/** Hide selected items from dropdown options */
@Input() hideSelected: boolean = false;
/** Select marked item when Tab key is pressed */
@Input() selectOnTab: boolean = false;
/** Open dropdown when Enter key is pressed */
@Input() openOnEnter: boolean;
/** Clear selection when backspace is pressed */
@Input() clearOnBackspace: boolean = true;
/** Prevent dropdown toggle on right mouse click */
@Input() preventToggleOnRightClick: boolean = false;
/** Mark first item as highlighted by default */
@Input() markFirst: boolean = true;
/** Allow search while composing (IME) */
@Input() searchWhileComposing: boolean = true;
/** Minimum search term length before searching */
@Input() minTermLength: number = 0;
/** Allow editing the search term */
@Input() editableSearchTerm: boolean = false;
// Dropdown positioning properties
/** Control dropdown open state */
@Input() isOpen: boolean;
/** Position of dropdown panel */
@Input() dropdownPosition: DropdownPosition = 'auto';
/** Element to append dropdown to (CSS selector or 'body') */
@Input() appendTo: string;
// Virtual scrolling properties
/** Enable virtual scrolling for large datasets */
@Input() virtualScroll: boolean = false;
/** Number of items to buffer when virtual scrolling */
@Input() bufferAmount: number = 4;
// Grouping properties
/** Group items by property name or function */
@Input() groupBy: string | ((item: any) => any);
/** Function to generate group values */
@Input() groupValue: GroupValueFn;
/** Allow selecting entire groups */
@Input() selectableGroup: boolean = false;
/** Include groups as part of the model */
@Input() selectableGroupAsModel: boolean = true;
/** Clear search term when adding new item */
@Input() get clearSearchOnAdd(): boolean { /* computed */ };
/** Allow deselecting items by clicking them again */
@Input() get deselectOnClick(): boolean { /* computed */ };
// Advanced properties
/** Custom search function */
@Input() searchFn: (term: string, item: any) => boolean;
/** TrackBy function for ngFor performance */
@Input() trackByFn: (index: number, item: any) => any;
/** Custom keydown handler function */
@Input() keyDownFn: (event: KeyboardEvent) => boolean;
/** Observable for typeahead functionality */
@Input() typeahead: Subject<string>;
/** Additional CSS classes */
@Input() ngClass: any;
/** Tab index for accessibility */
@Input() tabIndex: number;
/** Label ID for accessibility */
@Input() labelForId: string;
/** Additional input element attributes */
@Input() inputAttrs: {[key: string]: string} = {};
/** Enable tab focus on clear button */
@Input() tabFocusOnClearButton: boolean; // signal input
// Accessibility properties
/** ARIA label for the component */
@Input() ariaLabel: string | undefined;
/** ARIA labelledby attribute for accessibility */
@Input() ariaLabelledBy: string;
/** ARIA label for the dropdown */
@Input() ariaLabelDropdown: string = 'Options List';
// Events
/** Emitted when component loses focus */
@Output() blur: EventEmitter<any> = new EventEmitter();
/** Emitted when component gains focus */
@Output() focus: EventEmitter<any> = new EventEmitter();
/** Emitted when selection changes */
@Output() change: EventEmitter<any> = new EventEmitter();
/** Emitted when dropdown opens */
@Output() open: EventEmitter<void> = new EventEmitter();
/** Emitted when dropdown closes */
@Output() close: EventEmitter<void> = new EventEmitter();
/** Emitted when search term changes */
@Output() search: EventEmitter<{term: string, items: any[]}> = new EventEmitter();
/** Emitted when selection is cleared */
@Output() clear: EventEmitter<void> = new EventEmitter();
/** Emitted when item is added (including tags) */
@Output() add: EventEmitter<any> = new EventEmitter();
/** Emitted when item is removed */
@Output() remove: EventEmitter<any> = new EventEmitter();
/** Emitted during virtual scroll */
@Output() scroll: EventEmitter<{start: number, end: number}> = new EventEmitter();
/** Emitted when scrolled to end of list */
@Output() scrollToEnd: EventEmitter<void> = new EventEmitter();
// Public Properties
/** Array of currently selected items as NgOption objects */
selectedItems: NgOption[];
/** Array of raw selected values */
selectedValues: any[];
/** Whether the component currently has any selected values */
hasValue: boolean;
/** Current position of the dropdown panel */
currentPanelPosition: DropdownPosition;
/** Whether to show the add tag option */
showAddTag: boolean;
// Public Methods
/**
* Toggle the dropdown open/closed state
*/
toggle(): void;
/**
* Clear all selected items from the model
*/
clearModel(): void;
/**
* Select a specific item
* @param item - The NgOption to select
*/
select(item: NgOption): void;
/**
* Unselect a specific item
* @param item - The NgOption to unselect
*/
unselect(item: NgOption): void;
/**
* Toggle selection state of an item
* @param item - The NgOption to toggle
*/
toggleItem(item: NgOption): void;
/**
* Select the current tag being created
*/
selectTag(): void;
/**
* Determine if the clear button should be shown
* @returns true if clear button should be visible
*/
showClear(): boolean;
/**
* Focus on the clear button element
*/
focusOnClear(): void;
/**
* Determine if "no items found" message should be shown
* @returns true if no items message should be visible
*/
showNoItemsFound(): boolean;
/**
* Determine if "type to search" message should be shown
* @returns true if type to search message should be visible
*/
showTypeToSearch(): boolean;
/**
* Filter items based on search term
* @param term - The search term to filter by
*/
filter(term: string): void;
/**
* Handle composition start event (for IME input)
*/
onCompositionStart(): void;
/**
* Handle composition end event (for IME input)
* @param term - The composed search term
*/
onCompositionEnd(term: string): void;
/**
* Trigger change detection manually
*/
detectChanges(): void;
/**
* Clear a specific item from selection
* @param item - The item to clear
*/
clearItem(item: any): void;
/**
* TrackBy function for ngFor performance optimization
* @param index - The index of the item
* @param item - The NgOption item
* @returns Unique identifier for tracking
*/
trackByOption(index: number, item: NgOption): any;
}Usage Examples:
import { Component } from '@angular/core';
import { NgSelectComponent, NgOptionComponent } from '@ng-select/ng-select';
// Basic single select
@Component({
selector: 'app-basic-select',
standalone: true,
imports: [NgSelectComponent, NgOptionComponent],
template: `
<ng-select [(ngModel)]="selectedUser" bindLabel="name" bindValue="id">
<ng-option *ngFor="let user of users" [value]="user.id">
{{ user.name }}
</ng-option>
</ng-select>
`
})
export class BasicSelectComponent {
selectedUser: number;
users = [
{id: 1, name: 'Alice'},
{id: 2, name: 'Bob'}
];
}
// Multi-select with custom templates
@Component({
template: `
<ng-select
[(ngModel)]="selectedUsers"
[multiple]="true"
[closeOnSelect]="false"
[clearable]="true"
placeholder="Select users">
<ng-option *ngFor="let user of users" [value]="user">
<img [src]="user.avatar" width="20"> {{ user.name }}
</ng-option>
<ng-label-tmp let-item="item" let-clear="clear">
<span class="ng-value-label">{{ item.name }}</span>
<span class="ng-value-icon right" (click)="clear(item)">×</span>
</ng-label-tmp>
</ng-select>
`
})
export class MultiSelectComponent {
selectedUsers: any[] = [];
users = [
{id: 1, name: 'Alice', avatar: 'alice.jpg'},
{id: 2, name: 'Bob', avatar: 'bob.jpg'}
];
}
// Virtual scrolling for large datasets
@Component({
template: `
<ng-select
[(ngModel)]="selectedItems"
[items]="largeDataset"
bindLabel="name"
bindValue="id"
[virtualScroll]="true"
[bufferAmount]="20"
placeholder="Search in 10,000 items...">
</ng-select>
`
})
export class VirtualScrollComponent {
selectedItems: any[];
largeDataset = Array.from({length: 10000}, (_, i) => ({
id: i,
name: `Item ${i + 1}`
}));
}The NgSelectComponent automatically applies CSS classes and attributes through @HostBinding decorators:
/**
* CSS classes automatically applied by the component:
* - 'ng-select': Base component class
* - 'ng-select-single': Applied when multiple=false
* - 'ng-select-multiple': Applied when multiple=true
* - 'ng-select-searchable': Applied when searchable=true
* - 'ng-select-clearable': Applied when clearable=true
* - 'ng-select-opened': Applied when dropdown is open
* - 'ng-select-disabled': Applied when disabled=true
* - 'ng-select-focused': Applied when component has focus
* - 'ng-select-loading': Applied when loading=true
* - 'ng-select-readonly': Applied when readonly=true
* - 'ng-select-filtered': Applied when items are filtered
*
* Attributes:
* - 'role': Set to 'combobox' for accessibility
* - 'aria-expanded': Reflects dropdown open state
* - 'aria-haspopup': Set to 'listbox'
* - 'tabindex': Controlled by tabIndex input
*/Custom Search and Tag Management:
@Component({
template: `
<ng-select
[(ngModel)]="selectedItems"
[items]="filteredItems"
[addTag]="addTagFn"
[clearSearchOnAdd]="true"
[deselectOnClick]="true"
[editableSearchTerm]="true"
[searchWhileComposing]="false"
bindLabel="name"
bindValue="id"
placeholder="Search or add new items...">
</ng-select>
`
})
export class AdvancedSelectComponent {
selectedItems: any[] = [];
allItems = [
{id: 1, name: 'Existing Item 1'},
{id: 2, name: 'Existing Item 2'}
];
get filteredItems() {
// Custom filtering logic
return this.allItems;
}
addTagFn = (term: string) => {
return { id: Date.now(), name: term, isNew: true };
};
}Accessibility and Keyboard Navigation:
@Component({
template: `
<ng-select
[(ngModel)]="selected"
[items]="items"
[keyDownFn]="customKeyHandler"
[tabFocusOnClearButton]="true"
[ariaLabel]="'Select your preferred option'"
[ariaLabelDropdown]="'Available options list'"
bindLabel="label"
bindValue="value">
</ng-select>
`
})
export class AccessibleSelectComponent {
selected: any;
items = [
{value: 'option1', label: 'First Option'},
{value: 'option2', label: 'Second Option'}
];
customKeyHandler = (event: KeyboardEvent): boolean => {
// Custom keyboard handling
if (event.key === 'F2') {
// Custom behavior for F2 key
return false; // Prevent default
}
return true; // Allow default behavior
};
}Programmatic Control:
@Component({
template: `
<ng-select #selectRef [(ngModel)]="selected" [items]="items" bindLabel="name">
</ng-select>
<button (click)="toggleDropdown()">Toggle Dropdown</button>
<button (click)="clearAll()">Clear All</button>
<button (click)="selectFirst()">Select First Item</button>
<button (click)="focusClear()" [disabled]="!selectRef.showClear()">Focus Clear</button>
<div>
Selected Items: {{ selectRef.selectedItems?.length || 0 }}
Has Value: {{ selectRef.hasValue }}
Current Position: {{ selectRef.currentPanelPosition }}
</div>
`
})
export class ProgrammaticControlComponent {
@ViewChild('selectRef') selectComponent: NgSelectComponent;
selected: any;
items = [
{id: 1, name: 'Item 1'},
{id: 2, name: 'Item 2'},
{id: 3, name: 'Item 3'}
];
toggleDropdown() {
this.selectComponent.toggle();
}
clearAll() {
this.selectComponent.clearModel();
}
selectFirst() {
if (this.items.length > 0) {
this.selectComponent.select(this.items[0]);
}
}
focusClear() {
this.selectComponent.focusOnClear();
}
}Individual option component for dropdown items.
/**
* Component representing an individual option in the dropdown
*/
@Component({
selector: 'ng-option'
})
export class NgOptionComponent {
/** Value of this option */
@Input() value: any;
/** Whether this option is disabled */
@Input() disabled: boolean = false;
}Usage Example:
@Component({
template: `
<ng-select [(ngModel)]="selected">
<ng-option value="option1">Option 1</ng-option>
<ng-option value="option2" [disabled]="true">Option 2 (Disabled)</ng-option>
<ng-option value="option3">Option 3</ng-option>
</ng-select>
`
})
export class OptionsComponent {
selected: string;
}Dropdown panel container component handling the display and positioning of options.
/**
* Dropdown panel container component
*/
@Component({
selector: 'ng-dropdown-panel'
})
export class NgDropdownPanelComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
/** Items to display in the panel */
@Input() items: NgOption[];
/** Currently marked/highlighted item */
@Input() markedItem: NgOption;
/** Position of the panel relative to select */
@Input() position: DropdownPosition = 'auto';
/** Element to append panel to */
@Input() appendTo: string;
/** Buffer amount for virtual scrolling */
@Input() bufferAmount: number = 4;
/** Enable virtual scrolling */
@Input() virtualScroll: boolean = false;
/** Template for panel header */
@Input() headerTemplate: TemplateRef<any>;
/** Template for panel footer */
@Input() footerTemplate: TemplateRef<any>;
/** Current filter/search value */
@Input() filterValue: string;
/** Emitted when panel items are updated */
@Output() update: EventEmitter<any[]> = new EventEmitter();
/** Emitted during scrolling */
@Output() scroll: EventEmitter<{start: number, end: number}> = new EventEmitter();
/** Emitted when scrolled to end */
@Output() scrollToEnd: EventEmitter<void> = new EventEmitter();
/** Emitted when clicked outside panel */
@Output() outsideClick: EventEmitter<void> = new EventEmitter();
}