# ng-select URL: https://ng-select.github.io/ng-select/ ng-select is a lightweight all-in-one UI select, multiselect and autocomplete component for Angular. It is published as two npm packages: `@ng-select/ng-select` (the select component, template directives, and the `default`, `material` and `ant.design` themes) and `@ng-select/ng-option-highlight` (an optional directive that highlights the search term inside options). The component is standalone, uses OnPush change detection with signal-based inputs, integrates with Signal Forms, Reactive Forms and Template-driven Forms through `ControlValueAccessor`, and supports keyboard navigation, ARIA attributes, virtual scroll, typeahead, tagging, grouping and custom templates. - Current library version: 23.6.0 (Angular peer range ^22.0.0) - Install: `npm i @ng-select/ng-select @angular/cdk` (or pnpm/yarn) - Docs: https://ng-select.github.io/ng-select - Repository: https://github.com/ng-select/ng-select - Package: https://www.npmjs.com/package/@ng-select/ng-select # API Reference URL: https://ng-select.github.io/ng-select/reference/api/ Complete reference for `NgSelectComponent` and related directives and services. Most inputs and outputs below are demonstrated on the [Examples](/examples/data-sources/) pages. ## Forms integration ng-select supports Signal Forms through `[formField]`, Reactive Forms through `formControl` and `formControlName`, and Template-driven Forms through `ngModel`. See the [Forms examples](/examples/forms/) for complete examples in that order. ## Inputs | Input | Type | Default | Description | | --------------------------- | -------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [addTag] | `boolean \| ((term: string) => any \| Promise)` | `false` | Allows to create custom options. | | addTagText | `string` | `Add item` | Set custom text when using tagging | | appearance | `string` | `underline` | Allows to select dropdown appearance. Set to `outline` or `fill` for Material form-field styles (applies only to Material theme) | | appendTo | `string` | null | Append the dropdown overlay to any element using a css selector. Painting and positioning are unaffected; the target determines DOM containment (ancestor-scoped styles, focus enclosure). | | ariaLabel | `string` | `-` | Sets `aria-label` on the combobox input. | | ariaLabelDropdown | `string` | `Options List` | Sets `aria-label` on the dropdown listbox. | | bufferAmount | `number` | 4 | Used in virtual scrolling, the `bufferAmount` property controls the number of items preloaded in the background to ensure smoother and more seamless scrolling. | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | [closeOnSelect] | `boolean` | true | Whether to close the menu when a value is selected | | [closeOnScroll] | `boolean` | false | Close the dropdown when the page or an ancestor container scrolls. Default `false` | | clearAllText | `string` | `Clear all` | Set custom text for clear all icon title | | [clearable] | `boolean` | `true` | Allow to clear selected value. Default `true` | | [clearKeepsDisabledOptions] | `boolean` | `true` | Keep selected disabled options when the clear button is used. `clearModel()` always clears them. | | [clearOnBackspace] | `boolean` | `true` | With an empty search term, Backspace removes the last selected item (clears the value in single mode). Requires `[clearable]="true"` | | [compareWith] | `(a: any, b: any) => boolean` | `undefined` | A function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection(model). A boolean should be returned. When not set, options are matched by their `bindValue` property if set, otherwise by identity or equal labels. | | dropdownPosition | `bottom` \| `top` \| `left` \| `right` \| `auto` | `auto` | Set the dropdown position on open. `auto` opens below and flips above when there is not enough room | | [fixedPlaceholder] | `boolean` | `true` | Keep the placeholder rendered when an item is selected. Visibility is theme-dependent: the default and Ant Design themes hide it once a value is set, the Material theme floats it as a label | | [groupBy] | `string` \| `Function` | null | Allow to group items by key or function expression | | [groupValue] | `(groupKey: string \| any, children: any[]) => string \| any` | `-` | Function expression to provide group value | | [selectableGroup] | `boolean` | false | Allow to select group when groupBy is used | | [selectableGroupAsModel] | `boolean` | true | Indicates whether to select all children or group itself | | [items] | `Array` | `[]` | Items array | | [loading] | `boolean` | `false` | You can set the loading state from the outside (e.g. async items loading) | | loadingText | `string` | `Loading...` | Set custom text when for loading items | | labelForId | `string` | `-` | Id to associate control with label. | | [markFirst] | `boolean` | `true` | Marks first item as focused when opening/filtering. | | [isOpen] | `boolean` | `-` | Allows manual control of dropdown opening and closing. `true` - won't close. `false` - won't open. Binding a non-null value enables manual mode, where user interaction and `open()`/`close()` do nothing. | | maxSelectedItems | `number` | none | When multiple = true, allows to set a limit number of selection. | | [hideSelected] | `boolean` | `false` | Allows to hide selected items. | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | notFoundText | `string` | `No items found` | Set custom text when filter returns empty result | | placeholder | `string` | `-` | Placeholder text. | | removeText | `string` | `Remove` | Set custom text prefixed to the option label in the aria-label of the remove icon on selected values (multiple mode) | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | | [readonly] | `boolean` | `false` | Prevent user changes while preserving the current selection. | | [searchFn] | `(term: string, item: any) => boolean` | `null` | Allow to filter by custom search function | | [searchWhileComposing] | `boolean` | `true` | Whether items should be filtered while composition started | | [trackByFn] | `(item: any) => any` | `null` | Provide custom trackBy function | | [clearSearchOnAdd] | `boolean` | `true` | Clears search input when item is selected. Default `true`. Default `false` when **closeOnSelect** is `false` | | [deselectOnClick] | `boolean` | `false` | Deselects a selected item when it is clicked in the dropdown. Default `false`. Default `true` when **multiple** is `true` | | [editableSearchTerm] | `boolean` | `false` | Allow to edit search query if option selected. Default `false`. Works only if multiple is `false`. | | [selectOnTab] | `boolean` | `false` | Select marked dropdown item using tab. Default `false` | | [tabFocusOnClearButton] | `boolean` | `true` | Control tab navigation behavior for the clear button. Default `true` | | [openOnEnter] | `boolean` | `true` | Open dropdown using enter. Default `true` | | outsideClickEvent | `'click'` \| `'mousedown'` | `'click'` | Configure which DOM event type is used for outside click detection. Use `'mousedown'` to fix issues with backdrop/loading overlays that appear on dropdown open | | [popover] | `boolean` | `false` | **Deprecated — has no effect.** The CDK overlay renders in the native Popover API top layer automatically in supporting browsers. | | [typeahead] | `Subject` | `-` | Custom autocomplete or advanced filter. Emits on typing only; select/close/clear reset the input without emitting (use `(close)` / `(clear)` to react to those). | | [minTermLength] | `number` | `0` | Minimum term length to start a search. Should be used with `typeahead` | | typeToSearchText | `string` | `Type to search` | Set custom text when using Typeahead | | [virtualScroll] | `boolean` | false | Enable virtual scroll for better performance when rendering a lot of data | | [inputAttrs] | `{ [key: string]: string }` | `-` | Pass custom attributes to underlying `input` element | | [ngClass] | `string` \| `string[]` \| `Set` \| `Record` | `-` | Classes bound with `[ngClass]` are also mirrored onto the dropdown panel. | | [panelClass] | `string` \| `string[]` \| `Set` \| `Record` | `-` | Additional CSS classes applied to the dropdown panel. Merged with mirrored host `class`, `[class]`, and `[ngClass]` values. Preferred for panel-only styling. | | [tabIndex] | `number` | `-` | Set tabindex on the inner search `input` | | [preventToggleOnRightClick] | `boolean` | `false` | Prevent opening of ng-select on right mouse click | | [keyDownFn] | `($event: KeyboardEvent) => boolean` | `(_) => true` | Custom handler for control keys (Tab, Enter, Escape, Space, ArrowUp, ArrowDown, Backspace). Runs before the built-in handling; return `false` to suppress it | ## Outputs | Output | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | (add) | Fired when item is added while `[multiple]="true"`. Outputs added item | | (blur) | Fired on select blur | | (change) | Fired on selection change. Outputs the selected item (an array when `[multiple]="true"`) as the whole item object, not the `bindValue`-projected model value | | (close) | Fired on select dropdown close | | (clear) | Fired on clear icon click | | (focus) | Fired on select focus | | (search) | Fired while typing search term. Outputs search term with filtered items | | (open) | Fired on select dropdown open | | (remove) | Fired when a selected item is removed (× button, Backspace in multiple mode, or clicking a selected item with `[deselectOnClick]`). Outputs the removed item | | (scroll) | Fired when scrolled (only when `[virtualScroll]="true"`). Provides the start and end index of the currently available items. Can be used for loading more items in chunks before the user has scrolled all the way to the bottom of the list. | | (scrollToEnd) | Fired when scrolled to the end of items. Can be used for loading more items in chunks. | | (isOpenChange) | Emits `true`/`false` when the dropdown opens or closes. Not emitted in manual mode (a non-null `[isOpen]` / `[(isOpen)]` binding), so listen with `(isOpenChange)` alone. | | (itemsChange) | Model output for `[(items)]`. Emits only when ng-select sets `items` itself (options declared with ``) | | (bindLabelChange) | Exists because `bindLabel` is a `model()`. ng-select only writes it while applying `NgSelectConfig` defaults in its constructor, before listeners attach, so in practice it never emits; treat `[bindLabel]` as a one-way input | | (bindValueChange) | Exists because `bindValue` is a `model()`. ng-select only writes it while applying `NgSelectConfig` defaults in its constructor, before listeners attach, so in practice it never emits; treat `[bindValue]` as a one-way input | | (appearanceChange) | Exists because `appearance` is a `model()`. ng-select only writes it while applying `NgSelectConfig` defaults in its constructor, before listeners attach, so in practice it never emits; treat `[appearance]` as a one-way input | ## Methods Available on `NgSelectComponent` when accessed via a template reference variable or `@ViewChild`. | Name | Description | | ---------- | --------------------------------------------------------------------------------------------------------------- | | open | Opens the select dropdown panel | | close | Closes the select dropdown panel | | toggle | Opens the dropdown panel if closed, closes it if open | | focus | Focuses the select element | | blur | Blurs the select element | | filter | Takes a search term: sets it, filters the items (or pushes it to `typeahead`) and opens the dropdown | | select | Selects an `NgOption` (e.g. from `itemsList.findItem(value)`) | | unselect | Removes an `NgOption` from the selection | | toggleItem | Selects an `NgOption`, or unselects it if it is already selected and `deselectOnClick` is on | | selectTag | Creates a tag from the current search term (via `addTag` when it is a function) and selects it | | clearModel | Clears the selection, including disabled options, without emitting `(clear)`. No-op when `clearable` is `false` | ## ng-option Inputs of `NgOptionComponent`, used to declare options in HTML. The element content is the option label. | Input | Type | Default | Description | | ---------- | --------- | ----------- | ------------------------------------------------- | | [value] | `any` | `undefined` | Value bound to the option | | [disabled] | `boolean` | `false` | Whether the option is disabled and not selectable | ## Template directives Customize every part of the select with `ng-template` directives — see live examples on the [Templates](/examples/templates/) page. | Directive | Customizes | | ----------------------- | -------------------------------------------------------------------------------------------- | | `ng-label-tmp` | Label of each selected item (single select, or multiple select without `ng-multi-label-tmp`) | | `ng-multi-label-tmp` | Selected items (multi select) | | `ng-option-tmp` | Option rows in the dropdown | | `ng-optgroup-tmp` | Group headers when `groupBy` is used | | `ng-header-tmp` | Dropdown header | | `ng-footer-tmp` | Dropdown footer | | `ng-placeholder-tmp` | Placeholder | | `ng-notfound-tmp` | "No items found" message | | `ng-typetosearch-tmp` | "Type to search" message | | `ng-loadingtext-tmp` | Loading message | | `ng-loadingspinner-tmp` | Loading spinner | | `ng-clearbutton-tmp` | Clear button | | `ng-tag-tmp` | Tag row when `addTag` is enabled | ## Other | Name | Type | Description | | ------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ngOptionHighlight] | directive | Highlights search term in option. Accepts search term. Should be used on option element. [README](https://github.com/ng-select/ng-select/blob/master/src/ng-option-highlight/README.md) | | NgSelectConfig | configuration | Configuration provider for the NgSelect component. You can inject this service and provide application wide configuration. See [NgSelectConfig](#ngselectconfig). | ## NgSelectConfig Application-wide defaults, applied when the matching `ng-select` input is not set. | Property | Type | Default | Description | | -------------------- | -------------------------- | ---------------- | ------------------------------------------------------------ | | placeholder | `string` | `-` | Placeholder text | | fixedPlaceholder | `boolean` | `true` | Default for `[fixedPlaceholder]` | | notFoundText | `string` | `No items found` | Text when filter returns empty result | | typeToSearchText | `string` | `Type to search` | Text when using Typeahead | | addTagText | `string` | `Add item` | Text when using tagging | | loadingText | `string` | `Loading...` | Text when loading items | | clearAllText | `string` | `Clear all` | Clear all icon title | | removeText | `string` | `Remove` | Prefix of the remove icon aria-label (multiple mode) | | ariaLabelDropdown | `string` | `Options List` | aria-label of the dropdown listbox | | disableVirtualScroll | `boolean` | `true` | Disables virtual scroll unless `[virtualScroll]` is set | | openOnEnter | `boolean` | `true` | Open dropdown using enter | | appendTo | `string` | `-` | Default `appendTo` css selector for every select | | bindValue | `string` | `-` | Default `bindValue` | | bindLabel | `string` | `-` | Default `bindLabel` (falls back to `label`) | | appearance | `string` | `underline` | Default `appearance` | | clearSearchOnAdd | `boolean` | `-` | Default `[clearSearchOnAdd]` (falls back to `closeOnSelect`) | | deselectOnClick | `boolean` | `-` | Default `[deselectOnClick]` (falls back to `multiple`) | | tabFocusOnClear | `boolean` | `true` | Default `[tabFocusOnClearButton]` | | outsideClickEvent | `'click'` \| `'mousedown'` | `'click'` | Default `outsideClickEvent` | | closeOnScroll | `boolean` | `false` | Default `[closeOnScroll]` | # Installation URL: https://ng-select.github.io/ng-select/getting-started/installation/ ng-select is a lightweight all-in-one UI select, multiselect and autocomplete component for Angular. ## Install **npm** ```shell npm i @ng-select/ng-select @angular/cdk ``` **pnpm** ```shell pnpm i @ng-select/ng-select @angular/cdk ``` **yarn** ```shell yarn add @ng-select/ng-select @angular/cdk ``` ## Import ### Standalone Import `NgSelectComponent` and Signal Forms' `FormField` directive. Import any template directives you use alongside them: ```typescript import { NgLabelTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { FormField } from '@angular/forms/signals'; @Component({ selector: 'example', templateUrl: './example.component.html', styleUrl: './example.component.scss', imports: [FormField, NgLabelTemplateDirective, NgOptionTemplateDirective, NgSelectComponent], }) export class ExampleComponent {} ``` For Reactive Forms, import `ReactiveFormsModule`. For Template-driven Forms, import `FormsModule`. ### NgModule The standalone component is preferred. Existing NgModule applications can continue to import `NgSelectModule` with the forms module they use. This Template-driven Forms example uses `FormsModule`: ```typescript import { NgSelectModule } from '@ng-select/ng-select'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [AppComponent], imports: [NgSelectModule, FormsModule], bootstrap: [AppComponent], }) export class AppModule {} ``` ## Include a theme To allow customization and theming, the ng-select bundle includes only generic styles that are necessary for correct layout and positioning. To get the full look of the control, include one of the themes in your application styles: ```scss @import '@ng-select/ng-select/themes/default.theme.css'; // ... or @import '@ng-select/ng-select/themes/material.theme.css'; ``` ## Global configuration (optional) You can set global configuration and localization messages by injecting the `NgSelectConfig` service, typically in your root component: ```typescript constructor(private config: NgSelectConfig) { this.config.notFoundText = 'Custom not found'; // set the bindValue to global config when you use the same // bindValue in most places. this.config.bindValue = 'value'; } ``` # Data sources URL: https://ng-select.github.io/ng-select/examples/data-sources/ ng-select accepts items from an array of objects, inline `ng-option` elements, or asynchronous/observable data. ## Array of objects Bind an array of objects (or primitives) to the `items` input. **Example: `data-source-array-example`** ```ts title="data-source-array-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; import { DataService, Person } from '../data.service'; @Component({ selector: 'ng-data-source-array-example', templateUrl: './data-source-array-example.component.html', styleUrls: ['./data-source-array-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule], }) export class DataSourceArrayExampleComponent implements OnInit { private dataService = inject(DataService); people: Person[] = []; selectedPersonId = '5a15b13c36e7a7f00cf0d7cb'; selectedSimpleItem = 'Two'; simpleItems = []; ngOnInit() { this.dataService.getPeople().subscribe((items) => (this.people = items)); this.simpleItems = [true, 'Two', 3]; } } ``` ```html title="data-source-array-example.component.html"

You can also set array of objects as items input


While array of objects is the most common items source, you may want to set simple array of strings, numbers, booleans

``` ## Display data using ng-option For simple use cases, omit the items array and declare options directly in the template with `ng-option`. **Example: `data-source-options-example`** ```ts title="data-source-options-example.component.ts" import { JsonPipe } from '@angular/common'; import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptionComponent, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-data-source-options-example', templateUrl: './data-source-options-example.component.html', styleUrls: ['./data-source-options-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptionComponent, JsonPipe], }) export class DataSourceOptionsExampleComponent implements OnInit { selectedCars = [3]; cars = [ { id: 1, name: 'Volvo' }, { id: 2, name: 'Saab', disabled: true }, { id: 3, name: 'Opel' }, { id: 4, name: 'Audi' }, ]; ngOnInit() {} toggleDisabled() { const car: any = this.cars[1]; car.disabled = !car.disabled; } } ``` ```html title="data-source-options-example.component.html"

If you have simple use case, you can omit items array and bind options directly in html using ng-option component.


@for (car of cars; track car) { {{ car.name }} } Custom
Selected car ID: {{ selectedCars | json }} ``` ## Backend data with async pipe Load items from a backend as an observable and bind them with the `async` pipe. **Example: `data-source-backend-example`** ```ts title="data-source-backend-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { Observable } from 'rxjs'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-data-source-backend-example', templateUrl: './data-source-backend-example.component.html', styleUrls: ['./data-source-backend-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class DataSourceBackendExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPersonId = '5a15b13c36e7a7f00cf0d7cb'; ngOnInit() { this.people$ = this.dataService.getPeople(); } } ``` ```html title="data-source-backend-example.component.html"

Most common case is showing data from backend API and with ng-select this is extremely simple since you can bind directly to observable when using angular | async pipe


Selected: {{ selectedPersonId }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ----------- | ------------ | ------------ | ---------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [loading] | `boolean` | `-` | You can set the loading state from the outside (e.g. async items loading) | | loadingText | `string` | `Loading...` | Set custom text when for loading items | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Usage URL: https://ng-select.github.io/ng-select/getting-started/usage/ ng-select supports Signal Forms, Reactive Forms and Template-driven Forms in Angular 22 applications. Define options in your consuming component: ```typescript @Component({...}) export class ExampleComponent { readonly cars = [ { id: 1, name: 'Volvo' }, { id: 2, name: 'Saab' }, { id: 3, name: 'Opel' }, { id: 4, name: 'Audi' }, ]; } ``` ## Signal Forms Import `FormField` from `@angular/forms/signals`, create a form field tree, and bind the field—not the raw signal value: ```typescript import { signal } from '@angular/core'; import { form, FormField } from '@angular/forms/signals'; readonly carModel = signal({ selectedCarId: null as number | null }); readonly carForm = form(this.carModel); ``` ```html ``` ## Reactive Forms ```typescript import { FormControl, ReactiveFormsModule } from '@angular/forms'; readonly selectedCarId = new FormControl(null); ``` ```html ``` ## Template-driven Forms ```typescript import { FormsModule } from '@angular/forms'; selectedCarId: number | null = null; ``` ```html @for (car of cars; track car.id) { {{ car.name }} } ``` # Versions URL: https://ng-select.github.io/ng-select/reference/versions/ Each major version of ng-select targets a specific range of Angular versions. :::caution Avoid these releases: - **15.2.0, 16.0.0, 17.0.0, 18.0.0, 19.0.0, 20.0.0** contain unresolved issues. - **23.0.0** declares Angular 21 peer dependencies by mistake; use 23.0.1 or later. - **23.7.0 – 23.10.0** shipped the CDK Overlay migration (a breaking change) as minor releases. Use **23.11.0** or later for the 23.x line, or upgrade to **24.x**. ::: | Angular | ng-select | | ------------------- | :-------------------------------: | | >=22.0.0 <23.0.0 | v24.x.x (CDK Overlay), v23.x.x | | >=21.0.0 <22.0.0 | v21.x.x | | >=20.0.0 <21.0.0 | 15.0.1 – 15.1.3, v20.x (>=20.0.1) | | >=19.0.0 <20.0.0 | v14.x, 15.0.0 | | >=18.0.0 <19.0.0 | v13.x | | >=17.0.0 <18.0.0 | v12.x | | >=16.0.0 <17.0.0 | v11.x | | >=15.0.0 <16.0.0 | v10.x | | >=14.0.0 <15.0.0 | v9.x | | >=13.0.0 <14.0.0 | v8.x | | >=12.0.0 <13.0.0 | v7.x | | >=11.0.0 <12.0.0 | v6.x | | >=10.0.0 <11.0.0 | v5.x | | >=9.0.0 <10.0.0 | v4.x | | >=8.0.0 <9.0.0 | v3.x | | >=6.0.0 <8.0.0 | v2.x | | v5.x.x | v1.x | Release notes for every version are on [GitHub Releases](https://github.com/ng-select/ng-select/releases). # Browser support URL: https://ng-select.github.io/ng-select/reference/browser-support/ ng-select supports all browsers supported by Angular. For the current list, see the [Angular browser support guide](https://angular.dev/reference/versions#browser-support). This includes the following specific versions: | Browser | Supported versions | | ------- | ----------------------------------------- | | Chrome | 2 most recent versions | | Firefox | latest and extended support release (ESR) | | Edge | 2 most recent major versions | | Safari | 2 most recent major versions | | iOS | 2 most recent major versions | | Android | 2 most recent major versions | # Data bindings URL: https://ng-select.github.io/ng-select/examples/bindings/ ng-select can bind to plain values, whole objects, or nested properties using `bindLabel` and `bindValue`. ## Bind to default values **Example: `bindings-default-example`** ```ts title="bindings-default-example.component.ts" import { JsonPipe } from '@angular/common'; import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-bindings-default-example', templateUrl: './bindings-default-example.component.html', styleUrls: ['./bindings-default-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, JsonPipe], }) export class BindingsDefaultExampleComponent implements OnInit { defaultBindingsList = [ { value: 1, label: 'New York' }, { value: 2, label: 'London' }, { value: 3, label: 'Beijing' }, { value: 4, label: 'New Delhi', disabled: true }, { value: 5, label: 'Paris' }, ]; selectedCity = null; ngOnInit() { this.selectedCity = this.defaultBindingsList[0]; } } ``` ```html title="bindings-default-example.component.html"

By default ng-select binds to default label property for display, and keeps whole object as selected value


Selected city object: {{ selectedCity | json }} ``` ## Bind to custom values **Example: `bindings-custom-example`** ```ts title="bindings-custom-example.component.ts" import { JsonPipe } from '@angular/common'; import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-bindings-custom-example', templateUrl: './bindings-custom-example.component.html', styleUrls: ['./bindings-custom-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, JsonPipe], }) export class BindingsCustomExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Beijing' }, { id: 4, name: 'New Delhi', disabled: true }, { id: 5, name: 'Paris' }, ]; selectedCityId: number = null; ngOnInit() { this.selectedCityId = this.cities[0].id; } } ``` ```html title="bindings-custom-example.component.html"

You can provide custom property for display and selected value.


Selected city ID: {{ selectedCityId | json }} ``` ## Bind to nested properties **Example: `bindings-nested-example`** ```ts title="bindings-nested-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-bindings-nested-example', templateUrl: './bindings-nested-example.component.html', styleUrls: ['./bindings-nested-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule], }) export class BindingsNestedExampleComponent implements OnInit { countries = [ { id: 1, nested: { countryId: 'L', name: 'Lithuania' } }, { id: 2, nested: { countryId: 'U', name: 'USA' } }, { id: 3, nested: { countryId: 'A', name: 'Australia' } }, ]; selectedCountryId: string = null; ngOnInit() { this.selectedCountryId = this.countries[0].nested.countryId; } } ``` ```html title="bindings-nested-example.component.html"

Bind label and value to nested custom property


Selected country ID: {{ selectedCountryId }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ------------- | ----------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | placeholder | `string` | `-` | Placeholder text. | | [compareWith] | `(a: any, b: any) => boolean` | `(a, b) => a === b` | A function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection(model). A boolean should be returned. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Styling URL: https://ng-select.github.io/ng-select/getting-started/styling/ Every theme exposes its colours, sizes and shadows as CSS custom properties, so the recommended way to restyle ng-select is to set a few `--ng-select-*` variables. No `::ng-deep`, no specificity battles, and because custom properties resolve at runtime you can switch palettes without recompiling. ## Theming with CSS variables Import a theme as usual, then override the variables you care about: ```css @import '@ng-select/ng-select/themes/default.theme.css'; :root { --ng-select-highlight: #7aa2f7; --ng-select-border: #3a4152; --ng-select-bg: #1f2430; } ``` **Example: `css-variables-example`** ```ts title="css-variables-example.component.ts" import { ChangeDetectionStrategy, Component, OnDestroy, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; /** * The dropdown panel renders in the CDK overlay attached to ``, outside this * component's DOM subtree. Custom properties therefore have to be declared on an * ancestor of both — `` here — which is also how a real dark-mode toggle works. */ @Component({ selector: 'ng-css-variables-example', templateUrl: './css-variables-example.component.html', changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule], }) export class CssVariablesExampleComponent implements OnDestroy { protected readonly dark = signal(false); cities = [ { value: 1, label: 'New York' }, { value: 2, label: 'London' }, { value: 3, label: 'Paris' }, { value: 4, label: 'Tokyo' }, ]; selectedCity = 1; toggle(): void { this.dark.update((v) => !v); document.body.classList.toggle('ng-select-dark-demo', this.dark()); } ngOnDestroy(): void { document.body.classList.remove('ng-select-dark-demo'); } } ``` ```html title="css-variables-example.component.html"

No recompile, no ::ng-deep, no extra selectors — the button below only flips a class on <body> that redeclares a handful of --ng-select-* custom properties. Open the dropdown while the dark palette is on to see it apply to the panel too.



``` :::caution The dropdown panel renders in a CDK overlay attached to ``, not inside the `` element. Declare the variables on a common ancestor of both — `:root`, `html` or `body` — or the panel keeps the default palette. Scoping them to a wrapper `
` only restyles the select control. ::: ### Dark mode Because the variables are read at paint time, a dark palette is just a second declaration block: ```css @media (prefers-color-scheme: dark) { :root { --ng-select-bg: #1f2430; --ng-select-border: #3a4152; --ng-select-primary-text: #d8dee9; --ng-select-dropdown-bg: #1f2430; --ng-select-dropdown-option-text: #d8dee9; --ng-select-marked: #2a3145; --ng-select-selected: #2f3a52; } } ``` For a class-driven toggle (the Tailwind `dark:` convention), swap the media query for `html.dark { ... }`. :::note Some shades are derived from a base colour — `--ng-select-selected` and `--ng-select-marked` are tints of `--ng-select-highlight`, for example. CSS cannot recompute them, so changing `--ng-select-highlight` alone will not recolour them; set the derived variables too. The tables below mark which ones are derived. Overriding in Sass instead _does_ recompute them — see **Overriding with Sass** further down. ::: ### Variables — default theme | Variable | Default | Applies to | | -------------------------------------- | ----------------------------- | ---------------------------------- | | `--ng-select-highlight` | `#007eff` | Focused border | | `--ng-select-primary-text` | `#333` | Control text | | `--ng-select-disabled-text` | `#f9f9f9` | Disabled control background | | `--ng-select-border` | `#ccc` | Control and dropdown borders | | `--ng-select-border-radius` | `4px` | Control and dropdown corners | | `--ng-select-bg` | `#ffffff` | Control background | | `--ng-select-height` | `36px` | Control height | | `--ng-select-box-shadow` | focus ring | Focused control shadow | | `--ng-select-container-hover-shadow` | `0 1px 0 rgba(0, 0, 0, 0.06)` | Control hover shadow | | `--ng-select-value-padding-left` | `10px` | Value container inset | | `--ng-select-value-font-size` | `0.9em` | Multi-select chip text | | `--ng-select-value-text` | `#333` | Multi-select chip text | | `--ng-select-input-text` | `#000000` | Search input text | | `--ng-select-placeholder` \* | `#999999` | Placeholder text | | `--ng-select-selected` | `rgb(234.6, 244.68, 255)` | Selected option / chip background | | `--ng-select-selected-text` | `#333` | Selected option text | | `--ng-select-selected-hover` \* | `rgb(209.1, 231.78, 255)` | Chip remove-icon hover | | `--ng-select-selected-border` \* | `rgb(183.6, 218.88, 255)` | Chip remove-icon divider | | `--ng-select-marked` | `rgb(244.8, 249.84, 255)` | Keyboard-marked option background | | `--ng-select-marked-text` | `#333` | Keyboard-marked option text | | `--ng-select-arrow` \* | `#999999` | Arrow and clear icon | | `--ng-select-arrow-hover` \* | `#666666` | Arrow hover | | `--ng-select-arrow-active` \* | `#333333` | Arrow while open | | `--ng-select-clear` \* | `#999999` | Clear icon | | `--ng-select-clear-hover` | `#d0021b` | Clear icon hover / focus | | `--ng-select-border-dark` \* | `rgb(178.5, 178.5, 178.5)` | Top border while open | | `--ng-select-border-light` \* | `rgb(216.75, 216.75, 216.75)` | Bottom border while open | | `--ng-select-border-lighter` \* | `rgb(229.5, 229.5, 229.5)` | Panel edge merged with the control | | `--ng-select-dropdown-bg` | `#ffffff` | Panel background | | `--ng-select-dropdown-border` | `#ccc` | Panel border | | `--ng-select-dropdown-shadow` | `0 1px 0 rgba(0, 0, 0, 0.06)` | Panel shadow | | `--ng-select-dropdown-option-bg` | `#ffffff` | Option background | | `--ng-select-dropdown-option-text` | `rgba(0, 0, 0, 0.87)` | Option text | | `--ng-select-dropdown-option-disabled` | `#cccccc` | Disabled option text | | `--ng-select-dropdown-optgroup-text` | `rgba(0, 0, 0, 0.54)` | Group label text | | `--ng-select-dropdown-optgroup-marked` | `rgba(0, 0, 0, 0.54)` | Selected group label text | \* Derived shade — computed in Sass from a base colour. Set it explicitly when overriding that base colour through CSS variables. ### Variables — ant.design theme | Variable | Default | Applies to | | ------------------------------------ | ------------------------------ | --------------------------------- | | `--ng-select-highlight` | `#40a9ff` | Focused / open border | | `--ng-select-primary-text` | `rgba(0, 0, 0, 0.65)` | Control and option text | | `--ng-select-disabled-text` | `rgba(0, 0, 0, 0.25)` | Disabled text, arrow | | `--ng-select-disabled-bg` | `#f5f5f5` | Disabled background | | `--ng-select-border` | `#d9d9d9` | Control border | | `--ng-select-border-radius` | `4px` | Control, panel and option corners | | `--ng-select-bg` | `#ffffff` | Control and panel background | | `--ng-select-selected` | `rgba(24, 144, 255, 0.2)` | Focus ring | | `--ng-select-selected-bg` | `#fafafa` | Selected option background | | `--ng-select-marked` | `#e6f7ff` | Keyboard-marked option | | `--ng-select-placeholder` \* | `rgba(153, 153, 153, 0.65)` | Placeholder text | | `--ng-select-value-bg` | `#fafafa` | Multi-select chip background | | `--ng-select-value-border` \* | `rgb(232.3, 232.3, 232.3)` | Multi-select chip border | | `--ng-select-border-lighter` \* | `rgb(242.5, 242.5, 242.5)` | Panel edge next to the control | | `--ng-select-clear` \* | `#a6a6a6` | Clear icon | | `--ng-select-clear-bg` | `rgba(0, 0, 0, 0.25)` | Clear button background | | `--ng-select-clear-bg-hover` | `rgba(0, 0, 0, 0.45)` | Clear button hover / focus | | `--ng-select-clear-icon` | `#fff` | Clear glyph | | `--ng-select-dropdown-optgroup-text` | `rgba(0, 0, 0, 0.45)` | Group label text | | `--ng-select-dropdown-shadow` | `0 2px 8px rgba(0, 0, 0, .15)` | Panel elevation | \* Derived shade — computed in Sass from a base colour. Set it explicitly when overriding that base colour through CSS variables. ### Variables — material theme | Variable | Default | Applies to | | ---------------------------------- | --------------------------- | ------------------------------------ | | `--ng-select-highlight` | `#3f51b5` | Focused underline, label, selection | | `--ng-select-primary-text` | `rgba(0, 0, 0, 0.87)` | Control and option text | | `--ng-select-primary-light-text` | `rgba(255, 255, 255, 0.87)` | Chip remove-icon hover | | `--ng-select-secondary-text` | `rgba(0, 0, 0, 0.54)` | Placeholder, arrow, clear, group | | `--ng-select-secondary-light-text` | `rgba(255, 255, 255, 0.54)` | Chip remove icon | | `--ng-select-disabled-text` | `rgba(0, 0, 0, 0.38)` | Disabled text | | `--ng-select-disabled-value-text` | `rgba(0, 0, 0, 0.26)` | Disabled chip text | | `--ng-select-divider` | `rgba(0, 0, 0, 0.12)` | Outline, selected row, panel shadow | | `--ng-select-bg` | `#ffffff` | Panel background, chip text | | `--ng-select-underline` | `rgba(0, 0, 0, 0.42)` | Resting underline | | `--ng-select-marked` | `rgba(0, 0, 0, 0.04)` | Keyboard-marked option | | `--ng-select-fill-bg` | `rgba(0, 0, 0, 0.06)` | `ng-appearance-fill` background | | `--ng-select-fill-disabled-bg` | `rgba(0, 0, 0, 0.02)` | Disabled fill background | | `--ng-select-outline-width` | `1px` / `2px` | `ng-appearance-outline` border width | ## Overriding with Sass The Sass variables still work and seed the custom properties above, so existing setups keep compiling unchanged: ```scss @use '@ng-select/ng-select/scss/default.theme' with ( $ng-select-highlight: #7aa2f7, $ng-select-border: #3a4152 ); ``` The older `@import` form with variables declared beforehand also still works. Unlike the CSS variables, Sass overrides **do** recompute the derived shades: setting `$ng-select-highlight` alone also recolours `$ng-select-selected` and `$ng-select-marked`, because Sass evaluates `color.adjust()` at build time. Reach for this when you want one base colour to drive the whole palette; reach for the CSS variables when you need the palette to change at runtime. ## Overriding with selectors For anything the variables do not cover, override the styles with increased selector specificity or create your own theme. This applies if you are using no `ViewEncapsulation` or adding styles to a global stylesheet. The dropdown panel renders in a CDK overlay, not inside ``. Host `class`, `[class]`, and `[ngClass]` values are mirrored onto the panel root so a single class can style both the closed control and the open list. For panel-only styling — or when host and panel need different classes — use `panelClass`: ```html ``` ```css .ng-select.custom { border: 0px; min-height: 0px; border-radius: 0; } .ng-select.custom .ng-select-container { min-height: 0px; border-radius: 0; } .custom-panel.ng-dropdown-panel { max-height: 240px; } ``` When a shared class is enough for both the control and the panel: ```html ``` ```css .ng-select.custom { border: 0px; min-height: 0px; border-radius: 0; } .ng-select.custom .ng-select-container { min-height: 0px; border-radius: 0; } .custom.ng-dropdown-panel .ng-option { padding-left: 20px; } ``` If you are using `ViewEncapsulation`, you could use the special `::ng-deep` selector which will prevent scoping for nested selectors, although this is more of a workaround and we recommend the solution described above. ```css .ng-select.custom ::ng-deep .ng-select-container { min-height: 0px; border-radius: 0; } ``` :::caution Keep in mind that `::ng-deep` is deprecated and there is no alternative to it yet. See [angular/angular#17867](https://github.com/angular/angular/issues/17867). ::: ## Validation state Signal Forms keeps validation state on its field tree. To apply the familiar `ng-valid`, `ng-invalid`, `ng-touched`, and `ng-dirty` classes to ng-select, enable Angular's compatibility preset: ```typescript import { provideSignalFormsConfig } from '@angular/forms/signals'; import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat'; bootstrapApplication(AppComponent, { providers: [provideSignalFormsConfig({ classes: NG_STATUS_CLASSES })], }); ``` Reactive Forms and Template-driven Forms apply these classes automatically. Once the classes are enabled for the form system you use, show the error state with a custom style: ```css ng-select.ng-invalid.ng-touched .ng-select-container { border-color: #dc3545; box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px #fde6e8; } ``` # Change detection URL: https://ng-select.github.io/ng-select/getting-started/change-detection/ The ng-select component implements `OnPush` change detection, which means the dirty checking checks for immutable data types. That means if you do object mutations like: ```typescript this.items.push({ id: 1, name: 'New item' }); ``` the component will not detect a change. Instead you need to do: ```typescript this.items = [...this.items, { id: 1, name: 'New item' }]; ``` This will cause the component to detect the change and update. Some might have concerns that this is a pricey operation; however, it is much more performant than running `ngDoCheck` and constantly diffing the array. ## Zoneless change detection `@ng-select/ng-select` and `@ng-select/ng-option-highlight` fully support [zoneless change detection](https://angular.dev/guide/zoneless) — the default for new Angular apps since v21. No setup is required: the libraries do not depend on `zone.js` (it is not in their dependency graphs) and work identically whether your app is zoneless or still uses `zone.js`. Both modes are covered by the unit-test suite in CI, and this docs site runs zoneless. # Contributors URL: https://ng-select.github.io/ng-select/reference/contributors/ ng-select is maintained by a small team with help from the community. Want to join them? See the [contributing guide](https://github.com/ng-select/ng-select/blob/master/CONTRIBUTING.md). ## Active Maintainers ### Pavan Kumar Jadda (@pavankjadda) Pavan is the lead maintainer of ng-select. He is a full stack developer (Java, Angular and React) with 10 years of experience building enterprise Java and web applications. [GitHub](https://github.com/pavankjadda) · [Website](https://pavankjadda.dev) ### Sai Babu Raavi (@saibaburaavi) Sai Babu is a core maintainer of ng-select, contributing features, bug fixes, and releases. [GitHub](https://github.com/saibaburaavi) ## Contributors Everyone who has landed commits since 2024, most commits first. See the full history on [GitHub](https://github.com/ng-select/ng-select/graphs/contributors). - [Maheswari Pureti](https://github.com/mahipureti) (@mahipureti) - [Pankaj Parkar](https://github.com/pankajparkar) (@pankajparkar) - [Quentin Deroubaix](https://github.com/quentinderoubaix) (@quentinderoubaix) - [pkurcx](https://github.com/pkurcx) (@pkurcx) - [Giuseppe Liso](https://github.com/giusliso) (@giusliso) - [Bastien](https://github.com/bastienmoulia) (@bastienmoulia) - [xCMSjQuery](https://github.com/xCMSjQuery) (@xCMSjQuery) - [troehling](https://github.com/troehling) (@troehling) - [Tijs Moree](https://github.com/tijsmoree) (@tijsmoree) - [Terence Honles](https://github.com/terencehonles) (@terencehonles) - [Stéphane Roucheray](https://github.com/sroucheray) (@sroucheray) - [schwastek](https://github.com/schwastek) (@schwastek) - [Sascha Berger](https://github.com/saschaB91) (@saschaB91) - [Robert Maier-Silldorff](https://github.com/rmaiersilldorff) (@rmaiersilldorff) - [Oliver Günther](https://github.com/oliverguenther) (@oliverguenther) - [Nupin](https://github.com/npnsap) (@npnsap) - [Alexander Brandon Coles](https://github.com/myabc) (@myabc) - [Manos Kaparos](https://github.com/mnkprs) (@mnkprs) - [miccehedin](https://github.com/miccehedin) (@miccehedin) - [Olena Rozhko](https://github.com/lllen) (@lllen) - [Kratharth Hegde](https://github.com/kratharth-1999) (@kratharth-1999) - [Janne Vanhala](https://github.com/jpvanhal) (@jpvanhal) - [Róbert Kiss](https://github.com/ert78gb) (@ert78gb) - [Eloy Ortiz](https://github.com/eloyortiz) (@eloyortiz) - [Dmitriy Mishchenko](https://github.com/dmmishchenko) (@dmmishchenko) - [Chathulanka Gamage](https://github.com/cmgchess) (@cmgchess) - [ChanVuth Chea](https://github.com/cchvuth) (@cchvuth) - [Bobby Galli](https://github.com/bobbyg603) (@bobbyg603) - [Marko](https://github.com/Shaper9) (@Shaper9) - [SHKChan](https://github.com/SHKChan) (@SHKChan) - [Richard Jansma](https://github.com/RichardJansma) (@RichardJansma) - [Thomas Gnandt](https://github.com/Khartir) (@Khartir) - [Eugene](https://github.com/Eugeno) (@Eugeno) - [Dafnik](https://github.com/Dafnik) (@Dafnik) - [Chocobozzz](https://github.com/Chocobozzz) (@Chocobozzz) - [CazzanigaGianluca](https://github.com/CazzanigaGianluca) (@CazzanigaGianluca) # Forms URL: https://ng-select.github.io/ng-select/examples/forms/ ng-select supports Signal Forms, Reactive Forms and Template-driven Forms through the same control value accessor. ## Signal Forms Import `FormField` from `@angular/forms/signals` and bind a field from the tree returned by `form()`. `FormsModule` is not required for this integration. ```typescript import { signal } from '@angular/core'; import { form, FormField, required } from '@angular/forms/signals'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ imports: [FormField, NgSelectComponent], }) export class CityEditor { readonly model = signal({ cityId: null as number | null }); readonly cityForm = form(this.model, (path) => required(path.cityId)); } ``` ```html ``` The example also covers initial values before async items arrive, multiple selection, validation, disabled state, and recreating the control with `@if`. **Example: `forms-signal-example`** ```ts title="forms-signal-example.component.ts" import { JsonPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; import { disabled, form, FormField, minLength, required } from '@angular/forms/signals'; import { NgSelectComponent } from '@ng-select/ng-select'; interface City { id: number; name: string; } @Component({ selector: 'ng-forms-signal-example', templateUrl: './forms-signal-example.component.html', styleUrls: ['./forms-signal-example.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormField, NgSelectComponent, JsonPipe], }) export class FormsSignalExampleComponent { readonly cities: City[] = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Beijing' }, { id: 4, name: 'New Delhi' }, { id: 5, name: 'Paris' }, ]; readonly asyncCities = signal([]); readonly cityDisabled = signal(false); readonly multipleVisible = signal(true); readonly model = signal({ cityId: 2 as number | null, cityIds: [1, 3] as number[], }); readonly cityForm = form(this.model, (path) => { required(path.cityId, { message: 'Choose a city' }); minLength(path.cityIds, 1, { message: 'Choose at least one city' }); disabled(path.cityId, { when: () => this.cityDisabled() }); }); loadCities(): void { this.asyncCities.set([...this.cities]); } clearCities(): void { this.asyncCities.set([]); this.cityForm.cityIds().value.set([1, 3]); } toggleCityDisabled(): void { this.cityDisabled.update((value) => !value); } toggleMultipleVisible(): void { this.multipleVisible.update((value) => !value); } } ``` ```html title="forms-signal-example.component.html"
@if (cityForm.cityId().touched() && cityForm.cityId().invalid()) { {{ cityForm.cityId().errors()[0].message }} }
@if (multipleVisible()) { } @if (cityForm.cityIds().touched() && cityForm.cityIds().invalid()) { {{ cityForm.cityIds().errors()[0].message }} }

The form starts with two selected IDs before the item list exists. Load the items to see them mapped to options, and hide/show the control to see the values preserved.

{{ model() | json }}
``` ### Validation classes Signal Forms keeps validation state on the field tree and does not add Angular's legacy `ng-valid`, `ng-invalid`, `ng-touched`, and `ng-dirty` classes unless configured. Applications that use those classes with an ng-select theme can enable the compatibility preset: ```typescript import { provideSignalFormsConfig } from '@angular/forms/signals'; import { NG_STATUS_CLASSES } from '@angular/forms/signals/compat'; bootstrapApplication(AppComponent, { providers: [provideSignalFormsConfig({ classes: NG_STATUS_CLASSES })], }); ``` ## Reactive Forms Import `ReactiveFormsModule` and bind ng-select with `formControl`, `formControlName`, or a containing `FormGroup`. **Example: `forms-reactive-example`** ```ts title="forms-reactive-example.component.ts" import { JsonPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-forms-reactive-example', templateUrl: './forms-reactive-example.component.html', styleUrls: ['./forms-reactive-example.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, imports: [ReactiveFormsModule, NgSelectComponent, JsonPipe], }) export class FormsReactiveExampleComponent { readonly cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Beijing' }, { id: 4, name: 'New Delhi' }, { id: 5, name: 'Paris' }, ]; readonly cityForm = new FormGroup({ cityId: new FormControl(2, Validators.required), }); toggleDisabled(): void { const control = this.cityForm.controls.cityId; if (control.disabled) { control.enable(); } else { control.disable(); } } } ``` ```html title="forms-reactive-example.component.html"
@if (cityForm.controls.cityId.touched && cityForm.controls.cityId.invalid) { Choose a city }
{{ cityForm.getRawValue() | json }}
``` ### Single select with required validation **Example: `forms-single-select-example`** ```ts title="forms-single-select-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-forms-single-select-example', templateUrl: './forms-single-select-example.component.html', styleUrls: ['./forms-single-select-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, NgSelectComponent], }) export class FormsSingleSelectExampleComponent implements OnInit { private fb = inject(FormBuilder); private modalService = inject(NgbModal); heroForm: FormGroup; ages: any[] = [ { value: '<18', label: 'Under 18' }, { value: '18', label: '18' }, { value: '>18', label: 'More than 18' }, ]; ngOnInit() { this.heroForm = this.fb.group({ age: [null, Validators.required], }); } toggleAgeDisable() { if (this.heroForm.controls.age.disabled) { this.heroForm.controls.age.enable(); } else { this.heroForm.controls.age.disable(); } } showConfirm(content) { this.modalService.open(content); } } ``` ```html title="forms-single-select-example.component.html"

``` ### Multi select with clear button **Example: `forms-multi-select-example`** ```ts title="forms-multi-select-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-forms-multi-select-example', templateUrl: './forms-multi-select-example.component.html', styleUrls: ['./forms-multi-select-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, NgSelectComponent], }) export class FormsMultiSelectExampleComponent implements OnInit { private fb = inject(FormBuilder); heroForm: FormGroup; isCitiesControlVisible = true; cities: any[] = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Beijing' }, { id: 4, name: 'New Delhi (Disabled)', disabled: true }, { id: 5, name: 'Paris' }, ]; ngOnInit() { this.heroForm = this.fb.group({ selectedCitiesIds: [], }); } toggleCitiesControl() { this.isCitiesControlVisible = !this.isCitiesControlVisible; } clearCities() { this.heroForm.get('selectedCitiesIds').patchValue([]); } } ``` ```html title="forms-multi-select-example.component.html"
@if (isCitiesControlVisible) { }
``` ### Reactive form using ng-option **Example: `forms-with-options-example`** ```ts title="forms-with-options-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgOptionComponent, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-forms-with-options-example', templateUrl: './forms-with-options-example.component.html', styleUrls: ['./forms-with-options-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, NgSelectComponent, NgOptionComponent], }) export class FormsWithOptionsExampleComponent implements OnInit { private fb = inject(FormBuilder); basePath; heroForm: FormGroup; ngOnInit() { // The docs site is served under /ng-select (locally and on GitHub Pages); StackBlitz serves at the root. this.basePath = window.location.pathname.startsWith('/ng-select') ? '/ng-select' : ''; this.heroForm = this.fb.group({ heroId: 'batman', agree: null, }); } } ``` ```html title="forms-with-options-example.component.html"
Yes No
Batman Spider-Man & Goblin Thor
``` ### Reactive Forms using async data **Example: `forms-async-data-example`** ```ts title="forms-async-data-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgOptionTemplateDirective, NgSelectComponent, NgSelectComponent as NgSelectComponent_1 } from '@ng-select/ng-select'; import { delay } from 'rxjs/operators'; import { DataService } from '../data.service'; import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight'; @Component({ selector: 'ng-forms-async-data-example', templateUrl: './forms-async-data-example.component.html', styleUrls: ['./forms-async-data-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, NgSelectComponent_1, NgOptionTemplateDirective, NgOptionHighlightDirective], }) export class FormsAsyncDataExampleComponent implements OnInit { private fb = inject(FormBuilder); private dataService = inject(DataService); heroForm: FormGroup; albums = []; allAlbums = []; ngOnInit() { this.loadAlbums(); this.heroForm = this.fb.group({ album: '', }); } openSelect(select: NgSelectComponent) { select.open(); } closeSelect(select: NgSelectComponent) { select.close(); } selectAlbumsRange(from, to) { this.albums = this.allAlbums.slice(from, to); } selectFirstAlbum() { this.heroForm.get('album').patchValue(this.albums[0].id); } private loadAlbums() { this.dataService .getAlbums() .pipe(delay(500)) .subscribe((albums) => { this.allAlbums = albums; this.albums = [...this.allAlbums]; this.selectFirstAlbum(); }); } } ``` ```html title="forms-async-data-example.component.html"
Title: {{ item.title }}
Id: {{ item.id }} | UserId: {{ item.userId }}
Albums data from backend using HttpClient.
``` ### Reactive Forms with a custom template **Example: `forms-custom-template-example`** ```ts title="forms-custom-template-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { DataService } from '../data.service'; import { NgLabelTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight'; @Component({ selector: 'ng-forms-custom-template-example', templateUrl: './forms-custom-template-example.component.html', styleUrls: ['./forms-custom-template-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, ReactiveFormsModule, NgSelectComponent, NgLabelTemplateDirective, NgOptionTemplateDirective, NgOptionHighlightDirective], }) export class FormsCustomTemplateExampleComponent implements OnInit { private fb = inject(FormBuilder); private modalService = inject(NgbModal); private dataService = inject(DataService); heroForm: FormGroup; photos = []; ngOnInit() { this.loadPhotos(); this.heroForm = this.fb.group({ photo: '', }); } selectFirstPhoto() { this.heroForm.get('photo').patchValue(this.photos[0].thumbnailUrl); } openModal(content) { this.modalService.open(content); } changePhoto(photo) { this.heroForm.get('photo').patchValue(photo ? photo.thumbnailUrl : null); } togglePhotoDisabled() { const photo = this.heroForm.get('photo'); if (photo.disabled) { photo.enable(); } else { photo.disable(); } } private loadPhotos() { this.dataService.getPhotos().subscribe((photos) => { this.photos = photos; this.selectFirstPhoto(); }); } } ``` ```html title="forms-custom-template-example.component.html"
{{ item.title }} {{ item.title }} 5000 items with virtual scroll
``` ## Template-driven Forms Import `FormsModule`, bind with `[(ngModel)]`, and provide `name` when the control is inside a form. **Example: `forms-template-driven-example`** ```ts title="forms-template-driven-example.component.ts" import { JsonPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-forms-template-driven-example', templateUrl: './forms-template-driven-example.component.html', styleUrls: ['./forms-template-driven-example.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule, NgSelectComponent, JsonPipe], }) export class FormsTemplateDrivenExampleComponent { readonly cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Beijing' }, { id: 4, name: 'New Delhi' }, { id: 5, name: 'Paris' }, ]; readonly model = { cityId: 2 as number | null, }; } ``` ```html title="forms-template-driven-example.component.html"
@if (cityId.touched && cityId.invalid) { Choose a city }
{{ model | json }}
``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ---------------- | ------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [clearable] | `boolean` | `true` | Allow to clear selected value. Default `true` | | [readonly] | `boolean` | `false` | Set ng-select as readonly. | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | | placeholder | `string` | `-` | Placeholder text. | | labelForId | `string` | `-` | Id to associate control with label. | | appearance | `string` | `underline` | Allows to select dropdown appearance. Set to `outline` or `fill` for Material form-field styles (applies only to Material theme) | | dropdownPosition | `bottom` \| `top` \| `left` \| `right` \| `auto` | `auto` | Set the dropdown position on open | | clearAllText | `string` | `Clear all` | Set custom text for clear all icon title | | [selectOnTab] | `boolean` | `false` | Select marked dropdown item using tab. Default `false` | | [virtualScroll] | `boolean` | false | Enable virtual scroll for better performance when rendering a lot of data | Outputs used by the examples on this page: | Output | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | (change) | Fired on selection change. Outputs the selected item (an array when `[multiple]="true"`) as the whole item object, not the `bindValue`-projected model value | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Custom selection logic URL: https://ng-select.github.io/ng-select/getting-started/custom-selection-logic/ ng-select allows you to provide a custom selection implementation using `SELECTION_MODEL_FACTORY`. To override the [default](https://github.com/ng-select/ng-select/blob/master/src/ng-select/lib/selection-model.ts) logic, provide your factory method in your Angular module or application providers: ```typescript // app.config.ts / app.module.ts providers: [{ provide: SELECTION_MODEL_FACTORY, useValue: CustomSelectionFactory }]; // selection-model.ts export function CustomSelectionFactory() { return new CustomSelectionModel(); } export class CustomSelectionModel implements SelectionModel { // ... } ``` # Search and autocomplete URL: https://ng-select.github.io/ng-select/examples/search/ Ng-select filters items as you type and supports custom search functions, server-side typeahead, and autocomplete scenarios. ## Default search **Example: `search-default-example`** ```ts title="search-default-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-search-default-example', templateUrl: './search-default-example.component.html', styleUrls: ['./search-default-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent], }) export class SearchDefaultExampleComponent implements OnInit { private dataService = inject(DataService); people: Person[] = []; peopleLoading = false; ngOnInit() { this.loadPeople(); } private loadPeople() { this.peopleLoading = true; this.dataService.getPeople().subscribe((x) => { this.people = x; this.peopleLoading = false; }); } } ``` ```html title="search-default-example.component.html"

By default ng-select will search using label text. You can also use loading input to set loading state manually if [typeahead] is not used.

``` ## Search across multiple fields using [searchFn] **Example: `search-custom-example`** ```ts title="search-custom-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-search-custom-example', templateUrl: './search-custom-example.component.html', styleUrls: ['./search-custom-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, NgOptionTemplateDirective], }) export class SearchCustomExampleComponent implements OnInit { private dataService = inject(DataService); people: Person[] = []; peopleLoading = false; ngOnInit() { this.loadPeople(); } customSearchFn(term: string, item: Person) { term = term.toLowerCase(); return item.name.toLowerCase().indexOf(term) > -1 || item.gender.toLowerCase() === term; } private loadPeople() { this.peopleLoading = true; this.dataService.getPeople().subscribe((x) => { this.people = x; this.peopleLoading = false; }); } } ``` ```html title="search-custom-example.component.html"

Use search term and filter on custom fields. Type female to see only females.

{{ item.name }}
{{ item.gender }}
``` ## Custom server-side search **Example: `search-autocomplete-example`** ```ts title="search-autocomplete-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { concat, Observable, of, Subject } from 'rxjs'; import { DataService, Person } from '../data.service'; import { catchError, distinctUntilChanged, switchMap, tap } from 'rxjs/operators'; import { FormsModule } from '@angular/forms'; import { AsyncPipe, JsonPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-search-autocomplete-example', templateUrl: './search-autocomplete-example.component.html', styleUrls: ['./search-autocomplete-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe, JsonPipe], }) export class SearchAutocompleteExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; peopleLoading = false; peopleInput$ = new Subject(); selectedPersons: Person[] = [{ name: 'Karyn Wright' }, { name: 'Other' }]; ngOnInit() { this.loadPeople(); } trackByFn(item: Person) { return item.id; } private loadPeople() { this.people$ = concat( of([]), // default items this.peopleInput$.pipe( distinctUntilChanged(), tap(() => (this.peopleLoading = true)), switchMap((term) => this.dataService.getPeople(term).pipe( catchError(() => of([])), // empty list on error tap(() => (this.peopleLoading = false)), ), ), ), ); } } ``` ```html title="search-autocomplete-example.component.html"

Use typeahead to subscribe to search term and load async items.


Selected persons: {{ selectedPersons | json }}
``` ## Editable search value **Example: `search-editable-example`** ```ts title="search-editable-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { Observable } from 'rxjs'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-app-search-editable-example', templateUrl: './search-editable-example.component.html', styleUrls: ['./search-editable-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class SearchEditableExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPersonId = '5a15b13c36e7a7f00cf0d7cb'; ngOnInit() { this.people$ = this.dataService.getPeople(); } } ``` ```html title="search-editable-example.component.html"

By default, ng-select doesn't allow edit the current value for search. You can enable it by setting [editableSearchTerm] input to true. It's useful when you want to modify part of a long search query


Selected: {{ selectedPersonId }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ---------------------- | ---------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | | [searchFn] | `(term: string, item: any) => boolean` | `null` | Allow to filter by custom search function | | [typeahead] | `Subject` | `-` | Custom autocomplete or advanced filter. Emits on typing only; select/close/clear reset the input without emitting (use `(close)` / `(clear)` to react to those). | | [minTermLength] | `number` | `0` | Minimum term length to start a search. Should be used with `typeahead` | | typeToSearchText | `string` | `Type to search` | Set custom text when using Typeahead | | [editableSearchTerm] | `boolean` | `false` | Allow to edit search query if option selected. Default `false`. Works only if multiple is `false`. | | [searchWhileComposing] | `boolean` | `true` | Whether items should be filtered while composition started | | notFoundText | `string` | `No items found` | Set custom text when filter returns empty result | | [loading] | `boolean` | `-` | You can set the loading state from the outside (e.g. async items loading) | | [addTag] | `boolean \| ((term: string) => any \| Promise)` | `false` | Allows to create custom options. | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [hideSelected] | `boolean` | `false` | Allows to hide selected items. | | [trackByFn] | `(item: any) => any` | `null` | Provide custom trackBy function | | [clearSearchOnAdd] | `boolean` | `true` | Clears search input when item is selected. Default `true`. Default `false` when **closeOnSelect** is `false` | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Tags URL: https://ng-select.github.io/ng-select/examples/tags/ ng-select can create new options on the fly using the `addTag` input, letting users add items that are not in the list. ## Default tags **Example: `tags-default-example`** ```ts title="tags-default-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { JsonPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-tags-default-example', templateUrl: './tags-default-example.component.html', styleUrls: ['./tags-default-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, JsonPipe], }) export class TagsDefaultExampleComponent implements OnInit { selectedCompany; ngOnInit() {} } ``` ```html title="tags-default-example.component.html"

If option doesn't exist among items you can add new one using [addTag]="true"


Selected value: {{ selectedCompany | json }} ``` ## Custom tags **Example: `tags-custom-example`** ```ts title="tags-custom-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { JsonPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-tags-custom-example', templateUrl: './tags-custom-example.component.html', styleUrls: ['./tags-custom-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, JsonPipe], }) export class TagsCustomExampleComponent implements OnInit { selectedCompanies; companies: any[] = []; companiesNames = ['Uber', 'Microsoft', 'Flexigen']; ngOnInit() { this.companiesNames.forEach((c, i) => { this.companies.push({ id: i, name: c }); }); } addTagFn(name) { return { name: name, tag: true }; } } ``` ```html title="tags-custom-example.component.html"

By providing custom function to [addTag] you can modify result of new tag


Selected value: {{ selectedCompanies | json }} ``` ## Server side tags **Example: `tags-backend-example`** ```ts title="tags-backend-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent, NgTagTemplateDirective } from '@ng-select/ng-select'; @Component({ selector: 'ng-tags-backend-example', templateUrl: './tags-backend-example.component.html', styleUrls: ['./tags-backend-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgTagTemplateDirective], }) export class TagsBackendExampleComponent implements OnInit { selectedCompanies; companies: any[] = []; loading = false; companiesNames = ['Uber', 'Microsoft', 'Flexigen']; ngOnInit() { this.companiesNames.forEach((c, i) => { this.companies.push({ id: i, name: c }); }); } addTagPromise(name) { return new Promise((resolve) => { this.loading = true; // Simulate backend call. setTimeout(() => { resolve({ id: 5, name: name, valid: true }); this.loading = false; }, 1000); }); } } ``` ```html title="tags-backend-example.component.html"

[addTag] also accepts promise if you need to verify new option with backend

create new: {{ search }} ``` ## Tags without dropdown panel **Example: `tags-closed-dropdown-example`** ```ts title="tags-closed-dropdown-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-tags-closed-dropdown-example', templateUrl: './tags-closed-dropdown-example.component.html', styleUrls: ['./tags-closed-dropdown-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent], }) export class TagsClosedDropdownExampleComponent implements OnInit { constructor() {} ngOnInit() {} } ``` ```html title="tags-closed-dropdown-example.component.html"

Tagging without dropdown. Press enter to add item

``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | -------------- | ---------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------- | | [addTag] | `boolean \| ((term: string) => any \| Promise)` | `false` | Allows to create custom options. | | addTagText | `string` | `Add item` | Set custom text when using tagging | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | [hideSelected] | `boolean` | `false` | Allows to hide selected items. | | [isOpen] | `boolean` | `-` | Allows manual control of dropdown opening and closing. `true` - won't close. `false` - won't open. | | [items] | `Array` | `[]` | Items array | | [loading] | `boolean` | `-` | You can set the loading state from the outside (e.g. async items loading) | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [selectOnTab] | `boolean` | `false` | Select marked dropdown item using tab. Default `false` | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Templates URL: https://ng-select.github.io/ng-select/examples/templates/ ng-select lets you customize every part of the component — labels, options, headers, footers, and more — using `ng-template` directives. ## Custom label template **Example: `template-label-example`** ```ts title="template-label-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgLabelTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-label-example', templateUrl: './template-label-example.component.html', styleUrls: ['./template-label-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgLabelTemplateDirective], }) export class TemplateLabelExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; selectedCity = this.cities[0].name; ngOnInit() {} } ``` ```html title="template-label-example.component.html"

Custom selected item label using ng-label-tmp

{{ item.name }} ``` ## Custom placeholder template **Example: `template-placeholder-example`** ```ts title="template-placeholder-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgLabelTemplateDirective, NgPlaceholderTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-placeholder-example', templateUrl: './template-placeholder-example.component.html', styleUrls: ['./template-placeholder-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgPlaceholderTemplateDirective, NgLabelTemplateDirective], }) export class TemplatePlaceholderExampleComponent { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; selectedCity = undefined; placeholderAvatar = '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x'; } ``` ```html title="template-placeholder-example.component.html"

Custom placeholder using ng-placeholder-tmp

Select your city {{ item.name }} ``` ## Custom option template **Example: `template-option-example`** ```ts title="template-option-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight'; import { NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-option-example', templateUrl: './template-option-example.component.html', styleUrls: ['./template-option-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptionTemplateDirective, NgOptionHighlightDirective], }) export class TemplateOptionExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; selectedCity = this.cities[1].name; constructor() {} ngOnInit() {} } ``` ```html title="template-option-example.component.html"

Custom dropdown panel option template using ng-option-tmp

@if (item.name === 'London') {
{{ item.name }}
} @if (item.name !== 'London') {
{{ item.name }}
Card subtitle

Some quick example text to build

@if (item.name === 'Beijing') { }
}
``` ## Custom optgroup template **Example: `template-optgroup-example`** ```ts title="template-optgroup-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgLabelTemplateDirective, NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { NgOptionHighlightDirective } from '@ng-select/ng-option-highlight'; @Component({ selector: 'ng-template-optgroup-example', templateUrl: './template-optgroup-example.component.html', styleUrls: ['./template-optgroup-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgLabelTemplateDirective, NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgOptionHighlightDirective], }) export class TemplateOptgroupExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; selectedCity = this.cities[2].name; ngOnInit() {} } ``` ```html title="template-optgroup-example.component.html"

Custom label option and optgroup templates

{{ item.name }} City group logo {{ item.name }} ``` ## Custom header and footer template **Example: `template-header-footer-example`** ```ts title="template-header-footer-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { NgFooterTemplateDirective, NgHeaderTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-header-footer-example', templateUrl: './template-header-footer-example.component.html', styleUrls: ['./template-header-footer-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgHeaderTemplateDirective, NgFooterTemplateDirective], }) export class TemplateHeaderFooterExampleComponent implements OnInit { private dataService = inject(DataService); people = []; selectedPeople = []; ngOnInit() { this.dataService.getPeople().subscribe((items) => { this.people = items; }); } selectAll() { this.selectedPeople = this.people.map((x) => x.name); } unselectAll() { this.selectedPeople = []; } } ``` ```html title="template-header-footer-example.component.html"

Custom header and footer using ng-header-tmp and ng-footer-tmp

Selected count: {{ selectedPeople.length }}
Selected people: {{ selectedPeople }} ``` ## Custom info display templates **Example: `template-display-example`** ```ts title="template-display-example.component.ts" import { Component, EventEmitter, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { NgLoadingTextTemplateDirective, NgNotFoundTemplateDirective, NgSelectComponent, NgTypeToSearchTemplateDirective } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-display-example', templateUrl: './template-display-example.component.html', styleUrls: ['./template-display-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgTypeToSearchTemplateDirective, NgNotFoundTemplateDirective, NgLoadingTextTemplateDirective], }) export class TemplateDisplayExampleComponent implements OnInit { private dataService = inject(DataService); peopleTypeahead = new EventEmitter(); serverSideFilterItems = []; selectedPeople; ngOnInit() { this.serverSideSearch(); } private serverSideSearch() { this.peopleTypeahead .pipe( distinctUntilChanged(), debounceTime(300), switchMap((term) => this.dataService.getPeople(term)), ) .subscribe( (x) => { this.serverSideFilterItems = x; }, (err) => { console.log(err); this.serverSideFilterItems = []; }, ); } } ``` ```html title="template-display-example.component.html"

Custom not found, type to search and loading templates

Start typing...
No data found for "{{ searchTerm }}"
Fetching data for "{{ searchTerm }}"

Selected people: {{ selectedPeople }} ``` ## Custom search control **Example: `template-search-example`** ```ts title="template-search-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgHeaderTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-search-example', templateUrl: './template-search-example.component.html', styleUrls: ['./template-search-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgHeaderTemplateDirective], }) export class TemplateSearchExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; selectedCity = this.cities[0].name; constructor() {} ngOnInit() {} } ``` ```html title="template-search-example.component.html" ``` ## Custom loading spinner **Example: `template-loading-example`** ```ts title="template-loading-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { NgLoadingSpinnerTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-loading-example', templateUrl: './template-loading-example.component.html', styleUrls: ['./template-loading-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, NgLoadingSpinnerTemplateDirective], }) export class TemplateLoadingExampleComponent implements OnInit { cities = [ { id: 1, name: 'New York', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 2, name: 'London', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15' }, { id: 3, name: 'Beijing', avatar: '//www.gravatar.com/avatar/6acb7abf486516ab7fb0a6efa372042b?d=retro&r=g&s=15', }, { id: 4, name: 'New Delhi', avatar: '//www.gravatar.com/avatar/b0d8c6e5ea589e6fc3d3e08afb1873bb?d=retro&r=g&s=30 2x', }, { id: 5, name: 'Paris', avatar: '//www.gravatar.com/avatar/ddac2aa63ce82315b513be9dc93336e5?d=retro&r=g&s=15', }, ]; ngOnInit() {} } ``` ```html title="template-loading-example.component.html"

Custom loading spinner using ng-loadingspinner-tmp

``` ```scss title="template-loading-example.component.scss" .lds-ellipsis { display: inline-block; position: relative; width: 32px; height: 32px; margin-right: 10px; } .lds-ellipsis div { position: absolute; top: 14px; width: 8px; height: 8px; border-radius: 50%; background: #c2c2c2; animation-timing-function: cubic-bezier(0, 1, 1, 0); } .lds-ellipsis div:nth-child(1) { left: -9px; animation: lds-ellipsis1 0.6s infinite; } .lds-ellipsis div:nth-child(2) { left: -10px; animation: lds-ellipsis2 0.6s infinite; } .lds-ellipsis div:nth-child(3) { left: 2px; animation: lds-ellipsis2 0.6s infinite; } .lds-ellipsis div:nth-child(4) { left: 24px; animation: lds-ellipsis3 0.6s infinite; } @keyframes lds-ellipsis1 { 0% { transform: scale(0); } 100% { transform: scale(1); } } @keyframes lds-ellipsis3 { 0% { transform: scale(1); } 100% { transform: scale(0); } } @keyframes lds-ellipsis2 { 0% { transform: translate(0, 0); } 100% { transform: translate(19px, 0); } } ``` ## Custom clear button **Example: `template-clear-example`** ```ts title="template-clear-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgClearButtonTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-template-clear-example', templateUrl: './template-clear-example.component.html', styleUrls: ['./template-clear-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgClearButtonTemplateDirective], }) export class TemplateClearExampleComponent { cities = [ { id: 1, name: 'Clermont-Ferrand', }, { id: 2, name: 'Chamalières', }, { id: 3, name: 'Lyon', }, { id: 4, name: 'Compiègne', }, ]; selectedCity = this.cities[0].name; } ``` ```html title="template-clear-example.component.html"

Custom clear button using ng-clearbutton-tmp

Clear ``` ```scss title="template-clear-example.component.scss" .clb-select ::ng-deep .ng-clear-wrapper { margin-right: 8px; width: 50px; } ``` ## API | Template directive | Purpose | | ----------------------- | --------------------------------------------------------------------------------------------------------------------- | | `ng-label-tmp` | Customizes how the selected item's label is rendered in the select input | | `ng-placeholder-tmp` | Customizes the placeholder shown when no item is selected | | `ng-option-tmp` | Customizes how each option is rendered in the dropdown panel; exposes `item`, `index` and `searchTerm` | | `ng-optgroup-tmp` | Customizes how group headers are rendered when `groupBy` is used | | `ng-header-tmp` | Renders custom content at the top of the dropdown panel (e.g. select/unselect all buttons or a custom search control) | | `ng-footer-tmp` | Renders custom content at the bottom of the dropdown panel (e.g. selected count) | | `ng-typetosearch-tmp` | Customizes the message shown before the user starts typing when using `typeahead` | | `ng-notfound-tmp` | Customizes the message shown when the search returns no results; exposes `searchTerm` | | `ng-loadingtext-tmp` | Customizes the text shown while items are being loaded; exposes `searchTerm` | | `ng-loadingspinner-tmp` | Replaces the default loading spinner with custom markup | | `ng-clearbutton-tmp` | Replaces the default clear button with custom markup | ### Inputs | Input | Type | Default | Description | | ------------ | ---------------------- | ------- | ---------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [groupBy] | `string` \| `Function` | null | Allow to group items by key or function expression | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | | [loading] | `boolean` | `-` | You can set the loading state from the outside (e.g. async items loading) | | [typeahead] | `Subject` | `-` | Custom autocomplete or advanced filter. | | placeholder | `string` | `-` | Placeholder text. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Multiselect URL: https://ng-select.github.io/ng-select/examples/multiselect/ Setting `[multiple]="true"` enables multiselect mode, allowing users to select more than one item at a time. ## Multi select **Example: `multi-select-default-example`** ```ts title="multi-select-default-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-multi-select-default-example', templateUrl: './multi-select-default-example.component.html', styleUrls: ['./multi-select-default-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class MultiSelectDefaultExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPeople = [{ name: 'Karyn Wright' }]; ngOnInit() { this.people$ = this.dataService.getPeople(); } clearModel() { this.selectedPeople = []; } changeModel() { this.selectedPeople = [{ name: 'New person' }]; } } ``` ```html title="multi-select-default-example.component.html"

Select multiple elements

Selected value:
    @for (item of selectedPeople; track item.name) {
  • {{ item.name }}
  • }
``` ## Hidden selected items **Example: `multi-select-hidden-example`** ```ts title="multi-select-hidden-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-multi-select-hidden-example', templateUrl: './multi-select-hidden-example.component.html', styleUrls: ['./multi-select-hidden-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class MultiSelectHiddenExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPeople = []; ngOnInit() { this.people$ = this.dataService.getPeople(); } } ``` ```html title="multi-select-hidden-example.component.html"

Selected items won't appear among dropdown options

``` ## Limited number of selections **Example: `multi-select-limit-example`** ```ts title="multi-select-limit-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-multi-select-limit-example', templateUrl: './multi-select-limit-example.component.html', styleUrls: ['./multi-select-limit-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class MultiSelectLimitExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPeople = []; ngOnInit() { this.people$ = this.dataService.getPeople(); } clearModel() { this.selectedPeople = []; } } ``` ```html title="multi-select-limit-example.component.html"

Select multiple elements with a limit number of selections (e.g.: 3)

@if (selectedPeople.length === 3 && select.focused) {
Max selection reached
}
Selected value:
    @for (item of selectedPeople; track item) {
  • {{ item.name }}
  • }
``` ## Disabled select **Example: `multi-select-disabled-example`** ```ts title="multi-select-disabled-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-multi-select-disabled-example', templateUrl: './multi-select-disabled-example.component.html', styleUrls: ['./multi-select-disabled-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class MultiSelectDisabledExampleComponent implements OnInit { private dataService = inject(DataService); people$: Observable; selectedPeople = []; disable = true; ngOnInit() { this.people$ = this.dataService.getPeople(); this.setSelectedPeople(); } toggleModel() { if (this.selectedPeople.length > 0) { this.selectedPeople = []; } else { this.setSelectedPeople(); } } setSelectedPeople() { this.selectedPeople = [ { id: '5a15b13c2340978ec3d2c0ea', name: 'Rochelle Estes', disabled: true }, { id: '5a15b13c663ea0af9ad0dae8', name: 'Mendoza Ruiz' }, { id: '5a15b13c728cd3f43cc0fe8a', name: 'Marquez Nolan', disabled: true }, ]; } } ``` ```html title="multi-select-disabled-example.component.html"

Disabled multiple elements


``` ## Custom selected item template **Example: `multi-select-template-example`** ```ts title="multi-select-template-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { NgLabelTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { AsyncPipe } from '@angular/common'; @Component({ selector: 'ng-multi-select-template-example', templateUrl: './multi-select-template-example.component.html', styleUrls: ['./multi-select-template-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgLabelTemplateDirective, NgOptionTemplateDirective, AsyncPipe], }) export class MultiSelectTemplateExampleComponent implements OnInit { private dataService = inject(DataService); githubUsers$: Observable; selectedUsers = ['anjmao']; ngOnInit() { this.githubUsers$ = this.dataService.getGithubAccounts('anjm'); } } ``` ```html title="multi-select-template-example.component.html"

Custom template for each selected item using ng-label-tmp

{{ item.login }} {{ item.login }} ``` ## Custom selected items template **Example: `multi-select-custom-example`** ```ts title="multi-select-custom-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { NgMultiLabelTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { AsyncPipe, SlicePipe } from '@angular/common'; @Component({ selector: 'ng-multi-select-custom-example', templateUrl: './multi-select-custom-example.component.html', styleUrls: ['./multi-select-custom-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgMultiLabelTemplateDirective, AsyncPipe, SlicePipe], }) export class MultiSelectCustomExampleComponent implements OnInit { private dataService = inject(DataService); githubUsers$: Observable; selectedUsers = ['anjmao', 'anjmittu', 'anjmendoza']; ngOnInit() { this.githubUsers$ = this.dataService.getGithubAccounts('anjm'); } } ``` ```html title="multi-select-custom-example.component.html"

Custom template for all selected items using ng-multi-label-tmp

@for (item of items | slice: 0 : 2; track item) {
{{ item.login }}
} @if (items.length > 2) {
{{ items.length - 2 }} more...
}
``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ------------------ | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | maxSelectedItems | `number` | none | When multiple = true, allows to set a limit number of selection. | | [hideSelected] | `boolean` | `false` | Allows to hide selected items. | | [closeOnSelect] | `boolean` | true | Whether to close the menu when a value is selected | | [clearOnBackspace] | `boolean` | `true` | Clear selected values one by one when clicking backspace. Default `true` | | [clearSearchOnAdd] | `boolean` | `true` | Clears search input when item is selected. Default `true`. Default `false` when **closeOnSelect** is `false` | | [deselectOnClick] | `boolean` | `false` | Deselects a selected item when it is clicked in the dropdown. Default `false`. Default `true` when **multiple** is `true` | | removeText | `string` | `Remove` | Set custom text prefixed to the option label in the aria-label of the remove icon on selected values (multiple mode) | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | placeholder | `string` | `-` | Placeholder text. | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Multiselect checkbox URL: https://ng-select.github.io/ng-select/examples/multiselect-checkbox/ Render checkboxes inside dropdown options by using custom option templates in multiselect mode. ## Multi select with checkboxes **Example: `multi-checkbox-example`** ```ts title="multi-checkbox-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { map } from 'rxjs/operators'; import { FormsModule } from '@angular/forms'; import { JsonPipe, UpperCasePipe } from '@angular/common'; import { NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-multi-checkbox-example', templateUrl: './multi-checkbox-example.component.html', styleUrls: ['./multi-checkbox-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, NgOptionTemplateDirective, UpperCasePipe, JsonPipe], }) export class MultiCheckboxExampleComponent implements OnInit { private dataService = inject(DataService); people: Person[] = []; selectedPeople = []; ngOnInit() { this.dataService .getPeople() .pipe(map((x) => x.filter((y) => !y.disabled))) .subscribe((res) => { this.people = res; this.selectedPeople = [this.people[0].id, this.people[1].id]; }); } } ``` ```html title="multi-checkbox-example.component.html"

Select multiple elements using custom templates with checkboxes

{{ item.gender | uppercase }} {{ item.name }}
{{ selectedPeople | json }} ``` ## Grouped multi select with checkboxes **Example: `multi-checkbox-group-example`** ```ts title="multi-checkbox-group-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService, Person } from '../data.service'; import { map } from 'rxjs/operators'; import { FormsModule } from '@angular/forms'; import { NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { UpperCasePipe } from '@angular/common'; @Component({ selector: 'ng-multi-checkbox-group-example', templateUrl: './multi-checkbox-group-example.component.html', styleUrls: ['./multi-checkbox-group-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, NgOptionTemplateDirective, UpperCasePipe], }) export class MultiCheckboxGroupExampleComponent implements OnInit { private dataService = inject(DataService); people: Person[] = []; selectedPeople = []; ngOnInit() { this.dataService .getPeople() .pipe(map((x) => x.filter((y) => !y.disabled))) .subscribe((res) => { this.people = res; this.selectedPeople = [this.people[0].id]; }); } } ``` ```html title="multi-checkbox-group-example.component.html"

Group selects children

{{ item.gender | uppercase }} {{ item.name }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ------------------------ | ---------------------- | ------- | ---------------------------------------------------------------------------- | | [items] | `Array` | `[]` | Items array | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | [groupBy] | `string` \| `Function` | null | Allow to group items by key or function expression | | [selectableGroup] | `boolean` | false | Allow to select group when groupBy is used | | [selectableGroupAsModel] | `boolean` | true | Indicates whether to select all children or group itself | | [closeOnSelect] | `boolean` | true | Whether to close the menu when a value is selected | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Output events URL: https://ng-select.github.io/ng-select/examples/events/ ng-select emits output events for user interactions such as open, close, change, search, focus, blur, and scroll, so you can react to what happens inside the select. ## Output events **Example: `output-events-example`** ```ts title="output-events-example.component.ts" import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { JsonPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; interface Event { name: string; value: any; } @Component({ selector: 'ng-output-events-example', templateUrl: './output-events-example.component.html', styleUrls: ['./output-events-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, JsonPipe], }) export class OutputEventsExampleComponent { private dataService = inject(DataService); selectedItems: any; items = []; events: Event[] = []; constructor() { this.dataService.getPeople().subscribe((items) => { this.items = items; }); } onChange($event) { this.events.push({ name: '(change)', value: $event }); } onFocus($event: Event) { this.events.push({ name: '(focus)', value: $event }); } onBlur($event: Event) { this.events.push({ name: '(blur)', value: $event }); } onOpen() { this.events.push({ name: '(open)', value: null }); } onClose() { this.events.push({ name: '(close)', value: null }); } onAdd($event) { this.events.push({ name: '(add)', value: $event }); } onRemove($event) { this.events.push({ name: '(remove)', value: $event }); } onClear() { this.events.push({ name: '(clear)', value: null }); } onScrollToEnd($event) { this.events.push({ name: '(scrollToEnd)', value: $event }); } onSearch($event) { this.events.push({ name: '(search)', value: $event }); } } ``` ```html title="output-events-example.component.html" @if (events.length > 0) {



} @for (event of events; track event) {
{{ event.name }} - {{ event.value | json }}
} ``` ## API | Output | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | (add) | Fired when item is added while `[multiple]="true"`. Outputs added item | | (blur) | Fired on select blur | | (change) | Fired on selection change. Outputs the selected item (an array when `[multiple]="true"`) as the whole item object, not the `bindValue`-projected model value | | (close) | Fired on select dropdown close | | (clear) | Fired on clear icon click | | (focus) | Fired on select focus | | (search) | Fired while typing search term. Outputs search term with filtered items | | (open) | Fired on select dropdown open | | (remove) | Fired when item is removed while `[multiple]="true"` | | (scroll) | Fired when scrolled (only when `[virtualScroll]="true"`). Provides the start and end index of the currently available items. Can be used for loading more items in chunks before the user has scrolled all the way to the bottom of the list. | | (scrollToEnd) | Fired when scrolled to the end of items. Can be used for loading more items in chunks. | ### Methods | Name | Description | | ----- | -------------------------------- | | open | Opens the select dropdown panel | | close | Closes the select dropdown panel | | focus | Focuses the select element | | blur | Blurs the select element | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Virtual scroll URL: https://ng-select.github.io/ng-select/examples/virtual-scroll/ ng-select can render large lists efficiently by enabling `[virtualScroll]="true"`, which only renders the options currently visible in the dropdown viewport. ## Virtual scroll **Example: `virtual-scroll-example`** ```ts title="virtual-scroll-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { NgHeaderTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-virtual-scroll-example', templateUrl: './virtual-scroll-example.component.html', styleUrls: ['./virtual-scroll-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, NgHeaderTemplateDirective, NgOptionTemplateDirective], }) export class VirtualScrollExampleComponent implements OnInit { private http = inject(HttpClient); photos = []; photosBuffer = []; bufferSize = 50; numberOfItemsFromEndBeforeFetchingMore = 10; loading = false; ngOnInit() { this.http.get('https://jsonplaceholder.typicode.com/photos').subscribe((photos) => { this.photos = photos; this.photosBuffer = this.photos.slice(0, this.bufferSize); }); } onScrollToEnd() { this.fetchMore(); } onScroll({ end }) { if (this.loading || this.photos.length <= this.photosBuffer.length) { return; } if (end + this.numberOfItemsFromEndBeforeFetchingMore >= this.photosBuffer.length) { this.fetchMore(); } } private fetchMore() { const len = this.photosBuffer.length; const more = this.photos.slice(len, this.bufferSize + len); this.loading = true; // using timeout here to simulate backend API delay setTimeout(() => { this.loading = false; this.photosBuffer = this.photosBuffer.concat(more); }, 200); } } ``` ```html title="virtual-scroll-example.component.html"

In this example we are loading many items but only ~30 of them are rendered in the DOM. This allows to load as big data as you want.

Loaded {{ photosBuffer.length }} of {{ photos.length }} {{ index }} {{ item.title }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | --------------- | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [virtualScroll] | `boolean` | false | Enable virtual scroll for better performance when rendering a lot of data | | bufferAmount | `number` | 4 | Used in virtual scrolling, the `bufferAmount` property controls the number of items preloaded in the background to ensure smoother and more seamless scrolling. | | [items] | `Array` | `[]` | Items array | | [loading] | `boolean` | `-` | You can set the loading state from the outside (e.g. async items loading) | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | | placeholder | `string` | `-` | Placeholder text. | Outputs used by the examples on this page: | Output | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | (scroll) | Fired when scrolled (only when `[virtualScroll]="true"`). Provides the start and end index of the currently available items. Can be used for loading more items in chunks before the user has scrolled all the way to the bottom of the list. | | (scrollToEnd) | Fired when scrolled to the end of items. Can be used for loading more items in chunks. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Dropdown position URL: https://ng-select.github.io/ng-select/examples/dropdown-position/ Control where the dropdown panel opens relative to the select — top, bottom, or auto based on available space. ## Dropdown position **Example: `dropdown-position-example`** ```ts title="dropdown-position-example.component.ts" import { ChangeDetectionStrategy, Component } from '@angular/core'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-dropdown-position-example', templateUrl: './dropdown-position-example.component.html', styleUrls: ['./dropdown-position-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent], }) export class DropdownPositionExampleComponent{ cities = [ { value: 1, label: 'New York' }, { value: 2, label: 'London' }, { value: 3, label: 'Paris' }, ]; } ``` ```html title="dropdown-position-example.component.html"

By default the dropdown position is set to auto and will be shown above if there is not space placing it at the bottom.


You can force position to top, right, bottom, or left by setting dropdownPosition.



``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ---------------- | ------------------------------------------------ | ------- | --------------------------------------------------------------------------------------------------- | | dropdownPosition | `bottom` \| `top` \| `left` \| `right` \| `auto` | `auto` | Set the dropdown position on open. `auto` opens below and flips above when there is not enough room | | [items] | `Array` | `[]` | Items array | | [searchable] | `boolean` | `true` | Allow to search for value. Default `true` | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Fixed placeholder URL: https://ng-select.github.io/ng-select/examples/fixed-placeholder/ Keep the placeholder visible even while items are selected. ## Fixed placeholder **Example: `fixed-placeholder-example`** ```ts title="fixed-placeholder-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptionComponent, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-fixed-placeholder-example', templateUrl: './fixed-placeholder-example.component.html', styleUrls: ['./fixed-placeholder-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, NgSelectComponent, NgOptionComponent], }) export class FixedPlaceholderExampleComponent { isPlaceholderFixed: boolean = false; } ``` ```html title="fixed-placeholder-example.component.html"

In this example we show fixed placeholder when a value is selected. Use material theme to see it in action.

Bari Paris
``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ------------------ | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [fixedPlaceholder] | `boolean` | `true` | Keep the placeholder rendered when an item is selected. Visibility is theme-dependent: the default and Ant Design themes hide it once a value is set, the Material theme floats it as a label | | placeholder | `string` | `-` | Placeholder text. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Append to element URL: https://ng-select.github.io/ng-select/examples/append-to-element/ The dropdown panel renders in an Angular CDK overlay and paints in the browser's top layer wherever the native Popover API is supported, so the clipping and stacking problems that used to require `appendTo` are solved out of the box. The `appendTo` input now controls where the overlay lives **in the DOM**: pass any css selector when ancestor-scoped styles, a stacking context, or focus containment require the panel to be a descendant of a specific element. Positioning stays viewport-based either way, and the panel keeps following the select while scrolling. ## Clipped and scrollable containers **Example: `append-to-example`** ```ts title="append-to-example.component.ts" import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { DataService } from '../data.service'; import { FormsModule } from '@angular/forms'; import { AsyncPipe } from '@angular/common'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-append-to-example', templateUrl: './append-to-example.component.html', styleUrls: ['./append-to-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class AppendToExampleComponent implements OnInit { private dataService = inject(DataService); people: any = []; selected: any; selected2: any; selected3: any; selected4: any; ngOnInit() { this.people = this.dataService.getPeople(); } } ``` ```html title="append-to-example.component.html"
The dropdown panel renders in an Angular CDK overlay and paints in the browser's top layer wherever the native Popover API is supported, so clipping and stacking are handled without configuration. Use appendTo only when the panel must live inside a specific element in the DOM — e.g. to inherit ancestor-scoped styles or satisfy focus containment.

Containers with fixed height and hidden overflow used to clip the dropdown unless you appended it to another element. With overlay rendering the panel escapes the container automatically:


With appendTo the overlay becomes a DOM descendant of the selected element, so ancestor-scoped css (like a theme class on the container) applies to the panel. Positioning is unchanged:


Inside a scrollable container the panel stays anchored to the select while you scroll — no configuration needed:


Set closeOnScroll to close the panel instead when the page or an ancestor container scrolls:

``` ```scss title="append-to-example.component.scss" // as we are using ViewEncapsulation.ShadowDom we need to make sure all styles are accessible within this component // @import "~@ng-select/ng-select/themes/default.theme.css"; .overflow-box { padding: 5px; height: 100px; border: 1px solid #999; overflow: hidden; } .scrollable-box { position: relative; height: 400px; overflow: auto; } ``` ## Bootstrap modal The overlay renders in the browser's top layer (native Popover API) wherever supported, so the panel paints above the modal without any configuration: **Example: `modal-ng-bootstrap-example`** ```ts title="modal-ng-bootstrap-example.component.ts" import { AsyncPipe } from '@angular/common'; import { Component, TemplateRef, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgSelectComponent } from '@ng-select/ng-select'; import { DataService, Person } from '../data.service'; @Component({ selector: 'ng-modal-ng-bootstrap-example', templateUrl: './modal-ng-bootstrap-example.component.html', styleUrls: ['./modal-ng-bootstrap-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, AsyncPipe], }) export class ModalNgBootstrapExampleComponent { private readonly modalService = inject(NgbModal); readonly people$ = inject(DataService).getPeople(); selected: Person | null = null; openModal(content: TemplateRef): void { this.modalService.open(content); } } ``` ```html title="modal-ng-bootstrap-example.component.html"

ng-select inside an ng-bootstrap modal. The dropdown panel is appended to body by default so it stacks above the modal and is not clipped by the modal content.

``` ```scss title="modal-ng-bootstrap-example.component.scss" :host { display: block; } ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | --------------- | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | appendTo | `string` | null | Append the dropdown overlay to any element using a css selector. Painting and positioning are unaffected; the target determines DOM containment (ancestor-scoped styles, focus enclosure). | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | [closeOnScroll] | `boolean` | `false` | Close the dropdown when the page or an ancestor container scrolls. Default `false` | | placeholder | `string` | `-` | Placeholder text. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Popover URL: https://ng-select.github.io/ng-select/examples/popover/ The `popover` input is **deprecated and has no effect**: the dropdown panel always renders in an Angular CDK overlay, and the CDK uses the browser's native Popover API top layer automatically wherever it is supported. The panel can never be clipped by `overflow: hidden` containers and never ends up behind another element's stacking context — including native dialogs and Bootstrap modals. ## Top layer rendering **Example: `popover-example`** ```ts title="popover-example.component.ts" import { Component, inject, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; import { DataService, Person } from '../data.service'; import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'ng-popover-example', templateUrl: './popover-example.component.html', styleUrls: ['./popover-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule], }) export class PopoverExampleComponent { private dataService = inject(DataService); people = toSignal(this.dataService.getPeople()); selected1: Person; selected2: Person; } ``` ```html title="popover-example.component.html"
popover is deprecated and has no effect. The dropdown panel now always renders in an Angular CDK overlay, and the CDK uses the browser's native Popover API top layer automatically wherever it is supported. Remove the input from your templates.

Because the panel renders in the top layer, it can never be clipped by overflow: hidden containers and never ends up behind another element's stacking context — including native dialogs and Bootstrap modals:

Inside a container with overflow: hidden

Another clipped container — same behavior, no inputs required

In browsers without the Popover API, the panel falls back to the CDK overlay container (z-index: 1000). If something on your page still paints above it, raise the container in your global styles: .cdk-overlay-container { z-index: 1056; }
``` ```scss title="popover-example.component.scss" .clipped-box { padding: 10px; height: 80px; border: 1px solid #999; overflow: hidden; } .alert-info { background-color: #d1ecf1; border-color: #bee5eb; color: #0c5460; padding: 0.75rem 1.25rem; border-radius: 4px; ul { padding-left: 1.2rem; } a { color: #0c5460; } } ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ----------- | ------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | [popover] | `boolean` | `false` | **Deprecated — has no effect.** The CDK overlay renders in the native Popover API top layer automatically in supporting browsers. | | [items] | `Array` | `[]` | Items array | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | placeholder | `string` | `-` | Placeholder text. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Grouping URL: https://ng-select.github.io/ng-select/examples/grouping/ Use the `groupBy` input to group options by an item key or a function expression. ## Group by item key **Example: `group-default-example`** ```ts title="group-default-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { JsonPipe } from '@angular/common'; import { NgOptgroupTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-group-default-example', templateUrl: './group-default-example.component.html', styleUrls: ['./group-default-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, JsonPipe], }) export class GroupDefaultExampleComponent implements OnInit { selectedAccount = 'Adam'; accounts = [ { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } }, { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } }, { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } }, { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } }, { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } }, { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, ]; constructor() {} ngOnInit() {} } ``` ```html title="group-default-example.component.html"

You can group by item key providing key name as a string to groupBy input

{{ item.country || 'Unnamed group' }}
Selected: {{ selectedAccount | json }} ``` ## Group by function expression **Example: `group-function-example`** ```ts title="group-function-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptgroupTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { JsonPipe } from '@angular/common'; @Component({ selector: 'ng-group-function-example', templateUrl: './group-function-example.component.html', styleUrls: ['./group-function-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, JsonPipe], }) export class GroupFunctionExampleComponent implements OnInit { selectedAccounts = ['Michael']; accounts = [ { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } }, { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } }, { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } }, { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } }, { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } }, { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } }, { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } }, { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } }, { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, ]; constructor() {} groupByFn = (item) => item.child.state; groupValueFn = (_: string, children: any[]) => ({ name: children[0].child.state, total: children.length }); ngOnInit() {} } ``` ```html title="group-function-example.component.html"

Having more complex use case, you can use function expression as groupBy input. Also you can use [groupValue] to set value of grouped item.

{{ item.name }} {{ item.total }}
Selected: {{ selectedAccounts | json }} ``` ## Selectable groups **Example: `group-selectable-example`** ```ts title="group-selectable-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptgroupTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { JsonPipe } from '@angular/common'; @Component({ selector: 'ng-group-selectable-example', templateUrl: './group-selectable-example.component.html', styleUrls: ['./group-selectable-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, JsonPipe], }) export class GroupSelectableExampleComponent implements OnInit { selectedAccount = [{ country: 'Colombia' }]; selectedAccounts = [{ name: 'Adam' }]; accounts = [ { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } }, { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } }, { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } }, { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } }, { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } }, { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } }, { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } }, { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } }, { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' }, disabled: true }, { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, ]; ngOnInit() {} compareAccounts = (item, selected) => { if (selected.country && item.country) { return item.country === selected.country; } if (item.name && selected.name) { return item.name === selected.name; } return false; }; } ``` ```html title="group-selectable-example.component.html"

Groups can be made selectable. By default group acts as a separate option.

{{ item.country || 'Unnamed group' }}
Selected: {{ selectedAccount | json }}

Using [selectableGroupAsModel]="false" selecting group will select all of its children but not group itself.

{{ item.country || 'Unnamed group' }}
Selected: {{ selectedAccounts | json }} ``` ## Selectable groups with hidden selected items **Example: `group-selectable-hidden-example`** ```ts title="group-selectable-hidden-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptgroupTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; import { JsonPipe } from '@angular/common'; @Component({ selector: 'ng-group-selectable-hidden-example', templateUrl: './group-selectable-hidden-example.component.html', styleUrls: ['./group-selectable-hidden-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, JsonPipe], }) export class GroupSelectableHiddenExampleComponent implements OnInit { accounts = [ { name: 'Jill', email: 'jill@email.com', age: 15, country: undefined, child: { state: 'Active' } }, { name: 'Henry', email: 'henry@email.com', age: 10, country: undefined, child: { state: 'Active' } }, { name: 'Meg', email: 'meg@email.com', age: 7, country: null, child: { state: 'Active' } }, { name: 'Adam', email: 'adam@email.com', age: 12, country: 'United States', child: { state: 'Active' } }, { name: 'Homer', email: 'homer@email.com', age: 47, country: '', child: { state: 'Active' } }, { name: 'Samantha', email: 'samantha@email.com', age: 30, country: 'United States', child: { state: 'Active' } }, { name: 'Amalie', email: 'amalie@email.com', age: 12, country: 'Argentina', child: { state: 'Active' } }, { name: 'Estefanía', email: 'estefania@email.com', age: 21, country: 'Argentina', child: { state: 'Active' } }, { name: 'Adrian', email: 'adrian@email.com', age: 21, country: 'Ecuador', child: { state: 'Active' } }, { name: 'Wladimir', email: 'wladimir@email.com', age: 30, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Natasha', email: 'natasha@email.com', age: 54, country: 'Ecuador', child: { state: 'Inactive' } }, { name: 'Nicole', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Michael', email: 'michael@email.com', age: 15, country: 'Colombia', child: { state: 'Inactive' } }, { name: 'Nicolás', email: 'nicole@email.com', age: 43, country: 'Colombia', child: { state: 'Inactive' } }, ]; selectedAccounts = [{ country: 'Argentina' }, { name: 'Samantha' }]; constructor() {} ngOnInit() {} compareAccounts = (item, selected) => { if (selected.country && item.country) { return item.country === selected.country; } if (item.name && selected.name) { return item.name === selected.name; } return false; }; } ``` ```html title="group-selectable-hidden-example.component.html"

Selecting group will hide all of its items

{{ item.country || 'Unnamed group' }}
Selected: {{ selectedAccounts | json }} ``` ## Items with already grouped children array **Example: `group-children-example`** ```ts title="group-children-example.component.ts" import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { JsonPipe } from '@angular/common'; import { NgOptgroupTemplateDirective, NgOptionTemplateDirective, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-group-children-example', templateUrl: './group-children-example.component.html', styleUrls: ['./group-children-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [NgSelectComponent, FormsModule, NgOptgroupTemplateDirective, NgOptionTemplateDirective, JsonPipe], }) export class GroupChildrenExampleComponent implements OnInit { selectedProjects = []; projects = [ { id: 'p1', title: 'Project A', subprojects: [ { title: 'Subproject 1 of A', id: 's1p1' }, { title: 'Subproject 2 of A', id: 's2p1' }, ], }, { id: 'p2', title: 'Project B', subprojects: [ { title: 'Subproject 1 of B', id: 's1p2' }, { title: 'Subproject 2 of B', id: 's2p2' }, ], }, ]; constructor() {} ngOnInit() {} } ``` ```html title="group-children-example.component.html"

Group by children array. Note that when grouping by already grouped items ng-optgroup-tmp is required to display correct headers.

{{ item.title }} {{ item.title }}
Selected: {{ selectedProjects | json }} ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | ------------------------ | ----------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [groupBy] | `string` \| `Function` | null | Allow to group items by key or function expression | | [groupValue] | `(groupKey: string, children: any[]) => Object` | - | Function expression to provide group value | | [selectableGroup] | `boolean` | false | Allow to select group when groupBy is used | | [selectableGroupAsModel] | `boolean` | true | Indicates whether to select all children or group itself | | [hideSelected] | `boolean` | `false` | Allows to hide selected items. | | [items] | `Array` | `[]` | Items array | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | [closeOnSelect] | `boolean` | true | Whether to close the menu when a value is selected | | [compareWith] | `(a: any, b: any) => boolean` | `(a, b) => a === b` | A function to compare the option values with the selected values. The first argument is a value from an option. The second is a value from the selection(model). A boolean should be returned. | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | bindValue | `string` | `-` | Object property to use for selected model. By default binds to whole object. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs. # Material theme URL: https://ng-select.github.io/ng-select/examples/material/ ng-select ships with a bundled Material Design theme stylesheet (`@ng-select/ng-select/themes/material.theme.css`) that styles the select to match Angular Material form fields. ## Material appearances (default / outline / fill) **Example: `material-appearances-example`** ```ts title="material-appearances-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgOptionComponent, NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-material-appearances-example', templateUrl: './material-appearances-example.component.html', styleUrls: ['./material-appearances-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, NgSelectComponent, NgOptionComponent], }) export class MaterialAppearancesExampleComponent { cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Paris' }, { id: 4, name: 'Tokyo' }, { id: 5, name: 'New Delhi' }, ]; selectedDefault = this.cities[0]; selectedOutline = this.cities[0]; selectedFill = this.cities[0]; selectedOutlineEmpty: { id: number; name: string } | null = null; } ``` ```html title="material-appearances-example.component.html"

Compare Material theme appearances. Switch the demo theme to Material in the header to see underline, outline, and fill styles. Focus a searchable control and type to check that the search input stays vertically aligned with the selected value.

AAA BBB CCC
``` ```scss title="material-appearances-example.component.scss" @use 'sass:meta'; :host ::ng-deep { @include meta.load-css('../../../../ng-select/themes/material.theme'); } ``` ## Material outline and fill states **Example: `material-states-example`** ```ts title="material-states-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-material-states-example', templateUrl: './material-states-example.component.html', styleUrls: ['./material-states-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, NgSelectComponent], }) export class MaterialStatesExampleComponent { cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Paris' }, { id: 4, name: 'Tokyo' }, { id: 5, name: 'New Delhi' }, ]; selectedOutline = this.cities[1]; selectedFill = this.cities[1]; selectedOutlineDisabled = this.cities[0]; selectedFillDisabled = this.cities[0]; } ``` ```html title="material-states-example.component.html"

Disabled and clearable states for outline and fill appearances (Material theme).

``` ```scss title="material-states-example.component.scss" @use 'sass:meta'; :host ::ng-deep { @include meta.load-css('../../../../ng-select/themes/material.theme'); } ``` ## Material multiselect appearances **Example: `material-multiselect-example`** ```ts title="material-multiselect-example.component.ts" import { Component, ChangeDetectionStrategy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { NgSelectComponent } from '@ng-select/ng-select'; @Component({ selector: 'ng-material-multiselect-example', templateUrl: './material-multiselect-example.component.html', styleUrls: ['./material-multiselect-example.component.scss'], changeDetection: ChangeDetectionStrategy.Eager, imports: [FormsModule, NgSelectComponent], }) export class MaterialMultiselectExampleComponent { cities = [ { id: 1, name: 'New York' }, { id: 2, name: 'London' }, { id: 3, name: 'Paris' }, { id: 4, name: 'Tokyo' }, { id: 5, name: 'New Delhi' }, ]; selectedOutline = [this.cities[0], this.cities[1]]; selectedFill = [this.cities[0]]; } ``` ```html title="material-multiselect-example.component.html"

Multiple selection with Material outline and fill appearances.

``` ```scss title="material-multiselect-example.component.scss" @use 'sass:meta'; :host ::ng-deep { @include meta.load-css('../../../../ng-select/themes/material.theme'); } ``` ## API Inputs used by the examples on this page: | Input | Type | Default | Description | | --------------- | ------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------- | | appearance | `string` | `underline` | Allows to select dropdown appearance. Set to `outline` or `fill` for Material form-field styles (applies only to Material theme) | | bindLabel | `string` | `label` | Object property to use for label. Default `label` | | [clearable] | `boolean` | `true` | Allow to clear selected value. Default `true` | | [closeOnSelect] | `boolean` | true | Whether to close the menu when a value is selected | | [items] | `Array` | `[]` | Items array | | [multiple] | `boolean` | `false` | Allows to select multiple items. | | placeholder | `string` | `-` | Placeholder text. | See the `NgSelectComponent` API reference for the complete list of inputs and outputs.