ng-select2-component
Version:
An Angular select2 component.
556 lines (461 loc) • 33.3 kB
Markdown
[](https://badge.fury.io/js/ng-select2-component) [](https://www.npmjs.com/package/ng-select2-component) [](https://raw.githubusercontent.com/Harvest-Dev/ng-select2/master/LICENSE.md)
# Select2
This Angular CLI module it's a fork of [select2-component](https://github.com/plantain-00/select2-component) without Vue & React. For Vue or React, please use the original component.
## Installation
```
npm i ng-select2-component --save
```
## Requirements
- peerDependencies:
- `angular` `^22.0.0`
- `angular/cdk` `^22.0.0`
### Notes
| Version | For **Angular** | Notes |
| --------- | ---------------- | ----------------------------------------------------------- |
| `19.0.0` | 22 | Zoneless |
| `18.0.0` | 22 | Ivy / Stand-alone, native scroll (no `ngx-infinite-scroll`) |
| `17.3.2` | 18.1, 19, 20, 21 | Ivy / Stand-alone |
| `17.1.0` | 19 | Ivy / Stand-alone |
| `16.0.0` | 19 | Ivy / Module |
| `15.4.0` | 18 | Ivy |
| `14.0.1` | 17 | Ivy |
| `13.0.12` | 16.1 | Ivy |
| `12.1.0` | 16 | Ivy |
| `11.1.0` | 15 | Ivy |
| `10.0.0` | 14 | Ivy |
| `9.0.0` | 13 | Ivy |
| `8.1.0` | 10, 11 and 12 | View Engine |
| `7.3.1` | 7, 8 and 9 | View Engine |
## Demo
[See a demo and code generator](https://harvest-dev.github.io/ng-select2/dist/ng-select2/browser).
## Features
- **Selection**
- Single and multiple selection
- Limit the number of selections (multiple)
- Hide already selected items from the dropdown (multiple)
- Select all / Remove all button (multiple)
- Checkbox next to each option
- Drag & drop reordering of selected items (multiple)
- Resettable with configurable reset value
- Auto-create items not present in the list
- **Data representation**
- Options and option groups
- Declarative with `<ng-option>` and `<ng-group>` tags
- Infinite scroll
- Maximum results limit
- Grid layout (fixed column count or auto cell size)
- **Selection representation**
- Selection override: replace the selection area with custom text or a function
- Selection no-wrap: keep selected tags on a single line
- **Search**
- Local filtering with configurable minimum character threshold
- External / async search (`customSearchEnabled`)
- Highlight matched text in options (ignore the diacritics for: Latin, Japanese, Cyrillic, Greek, Arabic & Hebrew)
- **Keyboard**
- Standard keyboard navigation
- Native HTML `<select>` navigation mode (`nativeKeyboard`)
- **Templates**
- Custom rendering for options, groups and the selection area
- Per-item template ids (`templateId`, `templateSelectionId`)
- **Style**
- Four modes: default, material, borderless, no-style
- Fully themeable via CSS custom properties
- **Dropdown**
- Dropdown position: below, above, or auto
- Angular CDK Overlay mode
- **Forms**
- `ngModel` and `FormControl` binding
- `required`, `disabled`, `readonly`, `tabIndex`, `placeholder`
- **Accessibility**
- WAI-ARIA roles, live regions, `ariaLabelledby`, `ariaDescribedby`, hint slot
- **Angular**
- signals-based API (`input()` / `output()`)
- standalone component
- zoneless compatible (no `zone.js` dependency)
- Web Component compatible naming (`<ng-select2>`)
## Usage
### example
#### TS
```ts
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Select2, Select2Hint, Select2Label } from 'ng-select2-component';
@Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss'],
imports: [FormsModule, ReactiveFormsModule, Select2, Select2Hint, Select2Label],
})
class MyComponent {}
```
#### HTML template
```html
<select2 [data]="data" [value]="value" (update)="update($event)"></select2>
```
or
```html
<ng-select2 [data]="data" [value]="value" (update)="update($event)" />
```
or
```html
<ng-select2 [data]="data" [value]="value" (update)="update($event)">
<ng-select2-label>label</ng-select2-label>
<ng-select2-hint>hint</ng-select2-hint>
</ng-select2>
```
or with declarative `<ng-option>` / `<ng-group>` (no `[data]` binding needed):
```html
<ng-select2 [value]="value" (update)="update($event)">
<ng-group label="Fruits">
<ng-option value="apple">Apple</ng-option>
<ng-option value="banana">Banana</ng-option>
</ng-group>
<ng-option value="other">Other</ng-option>
</ng-select2>
```
### properties and events of the component
| name | type | default | description | required |
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `data` (required) | [`Select2Data`](#select2-data-structure) | | the data of the select2 | |
| `value` | [`Select2Value`](#select2-data-structure) | | initial value | |
| `minCharForSearch` | `number` | `0` | start the search when the number of characters is reached (`0` = unlimited) | |
| `minCountForSearch` | `number` | `6` | hide search box if `options.length < minCountForSearch` | |
| `displaySearchStatus` | `'default'`<br>`'hidden'`<br>`'always'` | `'default'` | display the search box (`default` : is based on `minCountForSearch`) | |
| `placeholder` | `string` | | the placeholder string if nothing selected | |
| `noResultMessage` | `string` | | the message string if no results when using the search field | |
| `customSearchEnabled` | `boolean` | `false` | will trigger `search` event, and disable inside filter | |
| `multiple` | `boolean` | `false` | select multiple options | |
| `multipleDrag` | `boolean` | `false` | drag'n drop list of items in selection | with `multiple` |
| `resettable` | `boolean` | `false` | add a button to reset value | |
| `resetSelectedValue` | `any` | `undefined` | selected option when × is clicked | |
| `autoCreate` | `boolean` | `false` | gives the possibility to add elements not present in the list. | |
| `limitSelection` | `number` | `0` | to limit multiple selection (`0` = unlimited) | |
| `hideSelectedItems` | `boolean` | `false` | remove selected values | with `multiple` |
| `resultMaxHeight` | `string` | `'200px'` | change the height size of results | |
| `maxResults` | `number` | `0` | maximum results limit (`0` = unlimited) | |
| `maxResultsMessage` | `string` | `'Too much result…'` | message when maximum result | |
| `grid` | `number` or `string` | | option by line in grid layout (empty or `0` = no grid layout)<br>`number`: item by line<br>`string`: minimal item width | |
| `listPosition` | `'below'`<br>`'above'`<br>`'auto'` ¹ | `'below'` | the position for the dropdown list | ¹ `'auto'`: only with `overlay` |
| `infiniteScroll` | `boolean` | `false` | enable infinite scroll on the dropdown list | |
| `infiniteScrollDistance` | `number` | `1.5` | scroll threshold as a fraction of the list height (e.g. `1.5` = bottom 15%) | |
| `infiniteScrollThrottle` | `number` | `150` | debounce time in ms between scroll event emissions | |
| `overlay` | `boolean` | `false` | active an overlay mode for dropdown list (with angular cdk). (See [Overlay](#overlay)) | |
| `styleMode` | `'default'`<br>`'material'`<br>`'noStyle'`<br>`'borderless'` | `'default'` | change style for material style or remove border and background color | |
| `templates` | [`Select2Template`](#select2-data-structure)<br>(see ”possible object” in [Templating](#templating)) | | use templates for formatting content (see [Templating](#templating)) | |
| `templateSelection` | `TemplateRef` | | use templates for formatting content (see [Templating](#templating)) | |
| `noLabelTemplate` | `boolean` | `false` | do not use the template in the selection, stay in text mode | |
| `selectionOverride` | [`Select2SelectionOverride`](#select2-data-structure) | | Replace selection by a text<br>`string`: `%size%` = total selected options<br>`function`: juste show the string | |
| `selectionNoWrap` | `boolean` | `false` | force selection on one line | |
| `showSelectAll` | `boolean` | `false` | Add an option to select all options | with `multiple` |
| `showOptionCheckbox` | `boolean` | `false` | Show a checkbox next to each option in the dropdown | |
| `selectAllText` | `string` | `'Select all'` | Text when all options as not selected | with `multiple` |
| `removeAllText` | `string` | `'Remove all'` | text when all options as selected | with `multiple` |
| `editPattern` | `(str: string) => string` | | use it for change the pattern of the filter search | |
| `nativeKeyboard` | `boolean` | `false` | use the keyboard navigation like native HTML select component | not with `multiple` |
| `highlightText` | `boolean` | `false` | use to highlight search text in dropdown options (with template see code generator) | |
| `ngModel`<br>`id`<br>`required`<br>`disabled`<br>`readonly`<br>`tabIndex` | | | just like a `select` control | |
| `(update)` | `(event: `[`Select2UpdateEvent`](#select2-data-structure)`) => void` | | triggered when user select an option | |
| `(open)` | `(event: Select2) => void` | | triggered when user open the options | |
| `(close)` | `(event: Select2) => void` | | triggered when user close the options | |
| `(focus)` | `(event: Select2) => void` | | triggered when user enters the component | |
| `(blur)` | `(event: Select2) => void` | | triggered when user leaves the component | |
| `(search)` | `(event: `[`Select2SearchEvent`](#select2-data-structure)`) => void` | | triggered when search text changed | with `customSearchEnabled` |
| `(scroll)` | `(event: `[`Select2ScrollEvent`](#select2-data-structure)`) => void` | | triggered when infinite scroll reaches the `up` or `down` threshold | |
| `(removeOption)` | `(event: `[`Select2RemoveEvent`](#select2-data-structure)`) => void` | | triggered when an option is removed from the list of selected options options list | with `multiple` |
| `(autoCreateItem)` | `(event: `[`Select2AutoCreateEvent`](#select2-data-structure)`) => void` | | triggered when a new item has been added | with `autoCreate` |
### select2 data structure
```ts
export interface Select2Group {
/** label of group */
label: string;
/** options list */
options: Select2Option[];
/** add classes */
classes?: string;
/** template id dropdown & selection if no templateSelectionId */
templateId?: string;
/** template data */
data?: any;
/** force left to right or right to left */
dir?: 'ltr' | 'rtl';
}
export interface Select2Option {
/** value */
value: Select2Value;
/** label of option */
label: string;
/** no selectable is disabled */
disabled?: boolean;
/** for identification */
id?: string;
/** add classes */
classes?: string;
/** template id dropdown & selection if no templateSelectionId */
templateId?: string;
/** template id for selection */
templateSelectionId?: string;
/** template data */
data?: any;
/** hide this option */
hide?: boolean;
/** force left to right or right to left */
dir?: 'ltr' | 'rtl';
}
export type Select2Value = string | number | boolean | object | null | undefined;
export type Select2UpdateValue = Select2Value | Select2Value[] | undefined | null;
export type Select2Data = (Select2Group | Select2Option)[];
export interface Select2UpdateEvent<U extends Select2UpdateValue = Select2Value> {
/** component */
readonly component: Select2;
/** current selected value */
readonly value: U | null;
/** selected option */
readonly options: Select2Option[] | null;
}
export interface Select2AutoCreateEvent<U extends Select2UpdateValue = Select2Value> {
/** component */
readonly component: Select2;
/** current selected value */
readonly value: U;
/** selected option */
readonly options: Select2Option[] | null;
}
export interface Select2SearchEvent<U extends Select2UpdateValue = Select2Value> {
/** component */
readonly component: Select2;
/** current selected value */
readonly value: U | null;
/** search text */
readonly search: string;
/** current data source */
readonly data: Select2Data;
/** method to call to update the data */
readonly filteredData: (data: Select2Data) => void;
}
export interface Select2RemoveEvent<U extends Select2UpdateValue = Select2Value> {
/** component */
readonly component: Select2;
/** current selected value */
readonly value: U;
/** remove */
readonly removedOption: Select2Option;
}
export interface Select2ScrollEvent {
/** component */
readonly component: Select2;
/** scroll way */
readonly way: 'up' | 'down';
/** search text */
readonly search: string;
/** current data */
readonly data: Select2Data;
}
export type Select2SelectionOverride = string | ((params: { size: number; options: Select2Option[] | null }) => string);
export type Select2Template = TemplateRef<any> | { [key: string]: TemplateRef<any> } | undefined;
```
### Templating
#### Unique template
```html
<ng-select2 [data]="data" [templates]="template">
<ng-template #template let-data="data"><strong>{{data?.color}}</strong>: {{data?.name}}</ng-template>
</ng-select2>
```
```ts
const data: Select2Data = [
{
value: 'heliotrope',
label: 'Heliotrope',
data: { color: 'white', name: 'Heliotrope' },
},
{
value: 'hibiscus',
label: 'Hibiscus',
data: { color: 'red', name: 'Hibiscus' },
},
];
```
#### Template group & option
```html
<ng-select2 [data]="data" [templates]="{option : option, group: group}">
<ng-template #option let-data="data">{{data?.name}}</ng-template>
<ng-template #group let-label="label">Group: {{label}}</ng-template>
</ng-select2>
```
No difference in data structure.
The template is defined by its type, option or group, automatically.
#### Template by templateId
```html
<ng-elect2 [data]="data" [templates]="{template1 : template1, template2: template2}">
<ng-template #template1 let-data="data">{{data?.name}}</ng-template>
<ng-template #template2 let-label="label" let-data="data">{{label}} : {{data?.color}}</ng-template>
</ng-elect2>
```
```ts
const data: Select2Data = [
{
value: 'heliotrope',
label: 'Heliotrope',
data: { color: 'white', name: 'Heliotrope' },
templateId: 'template1',
},
{
value: 'hibiscus',
label: 'Hibiscus',
data: { color: 'red', name: 'Hibiscus' },
templateId: 'template2',
},
];
```
#### Template for change the selection
```html
<ng-select2 [data]="data" [templateSelection]="templateSelection">
<ng-template #templateSelection let-data="data"><strong>{{ data?.color }}</strong> ({{ data?.name }})</ng-template>
</ng-select2>
```
#### Possible object
- `TemplateRef`
- `{template: TemplateRef}`
- `{option?: TemplateRef, group?: TemplateRef}`
- `{templateId1: TemplateRef, ...}`
In addition to the rendering templates of options and groups, in addition to going through the `templateSelection` attribute, it is possible to define that of the selection :
- `{templateSelection: TemplateRef}`
- `{optionSelection: TemplateRef}`
#### Priority order
For group or option:
- `'id'` (from item data `templateId`)
- `'group'` or `'option'`
- `'template'`
- `TemplateRef` (from html attribute `templates`)
- Default render
For the selection:
- `'id'` (from item data `templateSelectionId`)
- `'optionSelection'`
- `'templateSelection'`
- `TemplateRef` (from html attribute `templateSelection`)
- `'id'` (from item data `templateId`)
- `'option'`
- `'template'`
- `TemplateRef` (from html attribute `templates`)
- Default render
### Overlay
If the overlay mode is used / activated, add to the project root in CSS (with `ViewEncapsulation.None`)
```css
@import '~@angular/cdk/overlay-prebuilt.css';
```
## CSS variables
It's possible to change different colors (and more) with CSS variables without having to modify them with `::ng-deep` or other CSS rules :
```css
:root {
/* size */
--select2-single-height: 28px;
--select2-multiple-height: 28px;
/* label */
--select2-label-text-color: #000;
--select2-required-color: red;
/* selection */
--select2-selection-border-radius: 4px;
--select2-selection-background: #fff;
--select2-selection-disabled-background: #eee;
--select2-selection-border-color: #aaa;
--select2-selection-focus-border-color: #000;
--select2-selection-text-color: #111;
--select2-selection-line-height: 28px;
--select2-selection-padding: 0 0 0 8px;
/* selection (multiple) */
--select2-selection-multiple-gap: 2px 5px;
--select2-selection-multiple-padding: 2px 5px;
/* selection: choice item (multiple) */
--select2-selection-choice-background: #e4e4e4;
--select2-selection-choice-text-color: #000;
--select2-selection-choice-border-color: #aaa;
--select2-selection-choice-close-color: #999;
--select2-selection-choice-hover-close-color: #333;
/* placeholder */
--select2-placeholder-color: #999;
--select2-placeholder-overflow: ellipsis;
/* no result message */
--select2-no-result-color: #888;
--select2-no-result-font-style: italic;
/* no result message */
--select2-too-much-result-color: #888;
--select2-too-much-result-style: italic;
/* reset */
--select2-reset-color: #999;
/* arrow */
--select2-arrow-color: #888;
/* dropdown panel */
--select2-dropdown-background: #fff;
--select2-dropdown-border-color: #aaa;
--select2-dropdown-above-border-bottom: none;
--select2-dropdown-above-border-bottom-left-radius: 0;
--select2-dropdown-above-border-bottom-right-radius: 0;
--select2-dropdown-below-border-top: none;
--select2-dropdown-below-border-top-left-radius: 0;
--select2-dropdown-below-border-top-right-radius: 0;
/* overlay */
--select2-overlay-backdrop: transparent;
/* search field */
--select2-search-border-color: #aaa;
--select2-search-background: #fff;
--select2-search-border-radius: 0px;
/* dropdown option */
--select2-option-text-color: #000;
--select2-option-disabled-text-color: #999;
--select2-option-disabled-background: transparent;
--select2-option-selected-text-color: #000;
--select2-option-selected-background: #ddd;
--select2-option-highlighted-text-color: #fff;
--select2-option-highlighted-background: #5897fb;
--select2-option-group-text-color: gray;
--select2-option-group-background: transparent;
--select2-option-padding: 6px;
/* hint */
--select2-hint-text-color: #888;
/* highlight search text */
--select2-highlight-text-background: transparent;
--select2-highlight-text-color: inherit;
--select2-highlight-font-weight: 800;
--select2-highlighted-highlight-text-background: transparent;
--select2-highlighted-highlight-text-color: inherit;
--select2-highlighted-highlight-font-weight: 800;
/* for Material ------------------------------------------*/
--select2-material-underline: #ddd;
--select2-material-underline-active: #5a419e;
--select2-material-underline-disabled: linear-gradient(
to right,
rgba(0, 0, 0, 0.26) 0,
rgba(0, 0, 0, 0.26) 33%,
transparent 0
);
--select2-material-underline-invalid: red;
--select2-material-placeholder-color: rgba(0, 0, 0, 0.38);
--select2-material-selection-background: #ddd;
--select2-material-option-selected-background: rgba(0, 0, 0, 0.04);
--select2-material-option-highlighted-text-color: #000;
--select2-material-option-selected-text-color: #ff5722;
}
```
## Publishing the library
```sh
npm run build:lib
npm run publish:lib
```
## Update Demo
```sh
npm run build:demo
```
## Tests
```sh
# run tests
npm run test
npm run test:vitest
```
```sh
# run tests with coverage
npm run test:vitest:coverage
```
```sh
# run tests with watch
npm run test:vitest:watch
```
## License
Like Angular, this module is released under the permissive MIT license. Your contributions are always welcome.