Customize the native select with a powerful CoreUI Multi-Select component that changes initial element appearance and brings some new functionalities.
Available in Other JavaScript Frameworks
CoreUI Angular Multi Select Component is also available for Bootstrap, React, and Vue. Explore framework-specific implementations below:
Examples
import { Component } from '@angular/core';
import {
MultiSelectComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent,
MultiSelectOptionComponent
} from '@coreui/angular';
@Component({
selector: 'docs-multi-select-example',
templateUrl: './multi-select-example.component.html',
imports: [
MultiSelectComponent,
MultiSelectOptionComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent
]
})
export class MultiSelectExampleComponent {}<c-multi-select multiple>
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option selected>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
<c-multi-select-optgroup>
<c-multi-select-optgroup-label>Backend</c-multi-select-optgroup-label>
<c-multi-select-option>Django</c-multi-select-option>
<c-multi-select-option>Laravel</c-multi-select-option>
<c-multi-select-option>Node.js</c-multi-select-option>
</c-multi-select-optgroup>
</c-multi-select>import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptgroupComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-example-2',
templateUrl: './multi-select-example-2.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent, MultiSelectOptgroupComponent]
})
export class MultiSelectExample2Component {
frontend = [
{
value: 'Angular',
selected: true
},
{
value: 'Bootstrap',
disabled: true
},
{
value: 'React.js'
},
{
value: 'Vue.js'
}
];
backend = [
{
value: 'b1',
label: 'Django'
},
{
value: 'b2',
label: 'Laravel',
selected: true
},
{
value: 'b3',
label: 'Node.js'
}
];
}<c-multi-select multiple>
@for (option of frontend; track option.value) {
<c-multi-select-option
[value]="option.value"
[selected]="option.selected ?? false"
[disabled]="option.disabled"
>
{{ option.value }}
</c-multi-select-option>
}
<c-multi-select-optgroup label="Backend">
@for (option of backend; track option.value) {
<c-multi-select-option
[value]="option.value"
[selected]="option.selected ?? false"
>
{{ option.label }}
</c-multi-select-option>
}
</c-multi-select-optgroup>
</c-multi-select>Modes
Allow create options
The allowCreateOptions property allows users to create new options in addition to selecting pre-existing ones from a
list.
When this property is set to true, the user can type in a new option in the search input field of the multiselect component. If the option does not exist in the list, it will be created and added to the list of available options. This can be useful when the list of available options is not comprehensive or when the user needs to add and select an option that is not already available.
It’s worth noting that this feature may not always be relevant, depending on the specific use case of the multiselect component. In some cases, it may be preferable to restrict the user to selecting only pre-existing options, while in other cases, allowing for the creation of new options may be essential. It’s important to consider the specific requirements of your application when deciding whether to enable this feature.
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-allow-create-options',
templateUrl: './multi-select-allow-create-options.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectAllowCreateOptionsComponent {}<c-multi-select multiple allowCreateOptions>
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Clear search on select
The clearSearchOnSelect property is a Boolean attribute that can be used with the MultiSelect component in the CoreUI Angular library.
When clearSearchOnSelect is set to true, the search input field in the MultiSelect component will be cleared as soon as the user selects an option from the dropdown list. This means that the search query will be reset and the user will be able to start a new search immediately.
By default, clearSearchOnSelect is set to false, which means that the search input field will retain the user’s search query even after an option has been selected. This can be useful in situations where the user needs to select multiple options from the dropdown list that match the same search query.
To use the clearSearchOnSelect property with the MultiSelect component in the CoreUI Angular library, you simply need to set it to true or false as appropriate in your code.
In the following example, the clearSearchOnSelect property is set to true. The search input field will be cleared as soon as the user selects an option from the dropdown list.
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-clear-search-on',
templateUrl: './multi-select-clear-search-on.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectClearSearchOnComponent {}<c-multi-select multiple clearSearchOnSelect>
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
<c-multi-select-option>Svelte</c-multi-select-option>
<c-multi-select-option>Astro</c-multi-select-option>
</c-multi-select>Selection types
Counter
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-counter',
templateUrl: './multi-select-counter.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectCounterComponent {}<c-multi-select multiple selectionType="counter">
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Tags
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-tags',
templateUrl: './multi-select-tags.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectTagsComponent {}<c-multi-select multiple selectionType="tags">
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Text
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-text',
templateUrl: './multi-select-text.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectTextComponent {}<c-multi-select multiple selectionType="text">
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Select all
Added in v5.7.29With multiple enabled, a select all button is rendered in the dropdown header (turn it off with the selectAll property). The button works as a toggle: it selects every option and its label switches to deselectAllLabel, then deselects everything on the next click.
With the default selectAllStyle="checkbox" the button shows a tri-state indicator that mirrors the overall selection — none when nothing is selected, all when everything is, and indeterminate in between. Set selectAllStyle="text" for a plain text toggle instead.
import { Component } from '@angular/core';
import {
MultiSelectComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent,
MultiSelectOptionComponent
} from '@coreui/angular';
@Component({
selector: 'docs-multi-select-select-all',
templateUrl: './multi-select-select-all.component.html',
imports: [
MultiSelectComponent,
MultiSelectOptionComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent
]
})
export class MultiSelectSelectAllComponent {}<c-multi-select multiple>
<c-multi-select-option>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
<c-multi-select-optgroup>
<c-multi-select-optgroup-label>Backend</c-multi-select-optgroup-label>
<c-multi-select-option>Django</c-multi-select-option>
<c-multi-select-option>Laravel</c-multi-select-option>
<c-multi-select-option>Node.js</c-multi-select-option>
</c-multi-select-optgroup>
</c-multi-select>Acting on filtered options
By default (selectAllMode="all") the button acts on the full list, ignoring the current search. Set selectAllMode="filtered" to scope it to the options matched by the search filter. The label and the checkbox then answer “are all filtered options selected?”. With no active search every option matches, so this behaves exactly like "all".
To avoid a misleading “Select all” while a search is active, the label switches to selectFilteredLabel / deselectFilteredLabel (default Select filtered / Deselect filtered) whenever the search actually narrows the list — and falls back to selectAllLabel / deselectAllLabel when nothing is hidden.
Type into the search box below, then use select all — only the matching options are selected.
import { Component } from '@angular/core';
import {
MultiSelectComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent,
MultiSelectOptionComponent
} from '@coreui/angular';
@Component({
selector: 'docs-multi-select-select-all-mode',
templateUrl: './multi-select-select-all-mode.component.html',
imports: [
MultiSelectComponent,
MultiSelectOptionComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent
]
})
export class MultiSelectSelectAllModeComponent {}<c-multi-select multiple selectAllMode="filtered">
<c-multi-select-option>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
<c-multi-select-optgroup>
<c-multi-select-optgroup-label>Backend</c-multi-select-optgroup-label>
<c-multi-select-option>Django</c-multi-select-option>
<c-multi-select-option>Laravel</c-multi-select-option>
<c-multi-select-option>Node.js</c-multi-select-option>
</c-multi-select-optgroup>
</c-multi-select>Because the scope follows the search, a side effect is worth knowing: select all while a search is active (so the checkbox reads all), then clear the search — the checkbox drops to indeterminate, since the options that were hidden are back in the list and not selected.
Selection limit
Added in v5.7.29Use the selectionLimit property to limit how many options can be selected. Selecting further options is blocked once the limit is reached, while deselecting always works. The select all button stays enabled and selects options up to the limit, then reads as fully selected and toggles to deselect all. The selectionLimitReached output fires whenever a user tries to select more options than allowed — use it to show feedback.
import { Component, signal } from '@angular/core';
import {
AlertComponent,
MultiSelectComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent,
MultiSelectOptionComponent
} from '@coreui/angular';
@Component({
selector: 'docs-multi-select-selection-limit',
templateUrl: './multi-select-selection-limit.component.html',
imports: [
AlertComponent,
MultiSelectComponent,
MultiSelectOptionComponent,
MultiSelectOptgroupComponent,
MultiSelectOptgroupLabelComponent
]
})
export class MultiSelectSelectionLimitComponent {
readonly limitReached = signal(false);
}@if (limitReached()) {
<c-alert [(visible)]="limitReached" color="warning" dismissible fade>
You can select up to 3 options.
</c-alert>
}
<c-multi-select (selectionLimitReached)="limitReached.set(true)" [selectionLimit]="3" multiple>
<c-multi-select-option>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
<c-multi-select-optgroup>
<c-multi-select-optgroup-label>Backend</c-multi-select-optgroup-label>
<c-multi-select-option>Django</c-multi-select-option>
<c-multi-select-option>Laravel</c-multi-select-option>
<c-multi-select-option>Node.js</c-multi-select-option>
</c-multi-select-optgroup>
</c-multi-select>Single selection
Set the multiple boolean property to false and allow select only one element.
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-single-selection',
templateUrl: './multi-select-single-selection.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectSingleSelectionComponent {}<c-multi-select selectionType="text">
<c-multi-select-option>Angular</c-multi-select-option>
<c-multi-select-option disabled>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Coordinated selection
The selection of Angular select components can be coordinated by dynamically updating city options based on the selected country. To ensure synchronized selections, use the resetSelectionOnOptionsChange prop on the city select component to reset the selected city whenever the country changes.
import { Component, signal } from '@angular/core';
import {
ColComponent,
FormLabelDirective,
MultiSelectComponent,
MultiSelectOptionComponent,
RowComponent
} from '@coreui/angular';
import { cifDe, cifEs, cifGb, cifPl, cifUs } from '@coreui/icons';
import { IconDirective } from '@coreui/icons-angular';
@Component({
selector: 'docs-multi-select-coordinated-selection',
imports: [
RowComponent,
ColComponent,
MultiSelectComponent,
MultiSelectOptionComponent,
IconDirective,
FormLabelDirective
],
templateUrl: './multi-select-coordinated-selection.component.html'
})
export class MultiSelectCoordinatedSelectionComponent {
readonly flags: Record<string, string[]> = {
de: cifDe,
es: cifEs,
gb: cifGb,
pl: cifPl,
us: cifUs
};
readonly cities = signal<string[]>([]);
readonly countries = [
{
value: 'pl',
label: 'Poland',
cities: ['Warszawa', 'Kraków', 'Łódź', 'Wrocław', 'Poznań']
},
{
value: 'de',
label: 'Germany',
cities: ['Berlin', 'Hamburg', 'Munich', 'Cologne', 'Frankfurt']
},
{
value: 'us',
label: 'United States',
cities: ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix']
},
{
value: 'es',
label: 'Spain',
cities: ['Madrid', 'Barcelona', 'Valencia', 'Seville', 'Zaragoza']
},
{
value: 'gb',
label: 'United Kingdom',
cities: ['London', 'Birmingham', 'Manchester', 'Glasgow', 'Liverpool']
}
];
handleCountryChange(value: string) {
// console.log(value);
const cities = this.countries.find((c) => c.value === value)?.cities ?? [];
this.cities.set(cities);
}
handleCityChange(value: string) {
// console.log(value);
}
}<c-row>
<c-col [md]="6">
<label cLabel for="country">Select country</label>
<c-multi-select
(valueChange)="handleCountryChange($event)"
optionsStyle="text"
selectionType="text"
id="country">
@for (country of countries; track country.value) {
<c-multi-select-option [value]="country.value">
<div class="d-flex">
<svg [cIcon]="flags[country.value]" class="me-3" size="xl" />
{{ country.label }}
</div>
</c-multi-select-option>
}
</c-multi-select>
</c-col>
<c-col [md]="6">
<label cLabel for="city">Select city</label>
<c-multi-select
(valueChange)="handleCityChange($event)"
optionsStyle="text"
resetSelectionOnOptionsChange
selectionType="text"
id="city"
>
@for (city of cities(); track city) {
<c-multi-select-option [value]="city">
{{ city }}
</c-multi-select-option>
}
</c-multi-select>
</c-col>
</c-row>Disabled
Add the disabled boolean property to give it a grayed out appearance, remove pointer events, and prevent focusing.
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-disabled',
templateUrl: './multi-select-disabled.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectDisabledComponent {}<c-multi-select multiple disabled>
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option selected>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>Sizing
You may also choose from small and large multi selects to match our similarly sized text inputs.
import { Component } from '@angular/core';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-sizing',
templateUrl: './multi-select-sizing.component.html',
imports: [MultiSelectComponent, MultiSelectOptionComponent]
})
export class MultiSelectSizingComponent {}<c-multi-select multiple size="lg">
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>
<br>
<c-multi-select multiple >
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>
<br>
<c-multi-select multiple size="sm">
<c-multi-select-option selected>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>External search
Use (searchValueChange) to handle external search.
import { AsyncPipe, JsonPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { BehaviorSubject, Subject } from 'rxjs';
import { cilPaperclip } from '@coreui/icons';
import { OptionsService } from './options.service';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
import { IconDirective } from '@coreui/icons-angular';
@Component({
selector: 'docs-multi-select-external-search',
templateUrl: './multi-select-external-search.component.html',
providers: [OptionsService],
imports: [ReactiveFormsModule, MultiSelectComponent, MultiSelectOptionComponent, IconDirective, AsyncPipe, JsonPipe]
})
export class MultiSelectExternalSearchComponent {
icons = { cilPaperclip };
options;
readonly filteredOptions$ = new BehaviorSubject<any[]>([]);
readonly searchValue$ = new Subject<string>();
readonly formGroup = new FormGroup({
sampleSelect: new FormControl<string[]>(['4'])
});
constructor(private optionsService: OptionsService) {
this.options = optionsService.users.map((option) => ({
value: option.id,
label: option.last_name
}));
this.filteredOptions$.next([...this.options]);
this.searchValue$.subscribe((next) => {
const filtered = this.options.filter((option) =>
option.label.toLowerCase().startsWith(next.trimStart().toLowerCase())
);
this.filteredOptions$.next([...filtered]);
});
}
}<form [formGroup]="formGroup">
<p>Form value: {{ formGroup.value | json }}</p>
<hr />
<c-multi-select
(searchValueChange)="searchValue$.next($event)"
[options]="(filteredOptions$ | async) ?? []"
formControlName="sampleSelect"
multiple="true"
search="external"
visibleItems="8"
>
@for (option of filteredOptions$ | async; track option.value) {
<c-multi-select-option [value]="option.value">
<svg [cIcon]="icons.cilPaperclip" class="me-1" />
{{ option.label }}
</c-multi-select-option>
}
</c-multi-select>
</form>import { Injectable } from '@angular/core';
export interface IUsers {
id: string;
first_name: string;
last_name: string;
email: string;
country: string;
ip_address: string;
registered: string;
}
@Injectable()
export class OptionsService {
users: IUsers[] = [
{
id: '1',
first_name: 'Rowland',
last_name: 'Jumont',
email: '[email protected]',
country: 'China',
ip_address: '248.90.215.202',
registered: '2019-04-27'
},
{
id: '2',
first_name: 'Melloney',
last_name: 'Swindon',
email: '[email protected]',
country: 'Tunisia',
ip_address: '64.28.239.34',
registered: '2017-09-09'
},
{
id: '3',
first_name: 'Martica',
last_name: 'Farfolomeev',
email: '[email protected]',
country: 'Philippines',
ip_address: '135.56.179.89',
registered: '2021-02-26'
},
{
id: '4',
first_name: 'Modestia',
last_name: 'Batten',
email: '[email protected]',
country: 'Philippines',
ip_address: '119.63.79.34',
registered: '2022-03-24'
},
{
id: '5',
first_name: 'Peyter',
last_name: 'Andrusov',
email: '[email protected]',
country: 'Sweden',
ip_address: '127.179.144.121',
registered: '2017-09-15'
},
{
id: '6',
first_name: 'Brandise',
last_name: 'Lygoe',
email: '[email protected]',
country: 'Norway',
ip_address: '7.28.40.151',
registered: '2022-04-08'
},
{
id: '7',
first_name: 'Zitella',
last_name: 'Renshall',
email: '[email protected]',
country: 'Brazil',
ip_address: '191.192.158.32',
registered: '2020-10-23'
},
{
id: '8',
first_name: 'Cynthy',
last_name: 'Vaan',
email: '[email protected]',
country: 'Indonesia',
ip_address: '80.142.100.40',
registered: '2017-05-08'
},
{
id: '9',
first_name: 'Nicky',
last_name: 'Elgy',
email: '[email protected]',
country: 'China',
ip_address: '233.139.91.55',
registered: '2020-08-01'
},
{
id: '10',
first_name: 'Portie',
last_name: 'Van der Brugge',
email: '[email protected]',
country: 'Netherlands',
ip_address: '77.3.161.172',
registered: '2020-11-25'
},
{
id: '11',
first_name: 'Melessa',
last_name: 'Burgill',
email: '[email protected]',
country: 'China',
ip_address: '233.167.158.162',
registered: '2023-01-22'
},
{
id: '12',
first_name: 'Roman',
last_name: 'Tomowicz',
email: '[email protected]',
country: 'United States',
ip_address: '85.13.181.7',
registered: '2021-06-23'
},
{
id: '13',
first_name: 'Velvet',
last_name: 'Swafford',
email: '[email protected]',
country: 'Indonesia',
ip_address: '34.148.241.247',
registered: '2018-03-25'
},
{
id: '14',
first_name: 'Benoite',
last_name: 'Langelaan',
email: '[email protected]',
country: 'Albania',
ip_address: '138.86.125.234',
registered: '2021-06-29'
},
{
id: '15',
first_name: 'Chantalle',
last_name: 'Enriques',
email: '[email protected]',
country: 'Brazil',
ip_address: '24.210.20.220',
registered: '2017-04-18'
},
{
id: '16',
first_name: 'Enid',
last_name: 'Dulin',
email: '[email protected]',
country: 'China',
ip_address: '209.211.189.165',
registered: '2022-07-13'
},
{
id: '17',
first_name: 'Vasilis',
last_name: 'Shew',
email: '[email protected]',
country: 'Pakistan',
ip_address: '84.141.13.86',
registered: '2021-07-30'
},
{
id: '18',
first_name: 'Felice',
last_name: 'Lawrence',
email: '[email protected]',
country: 'China',
ip_address: '212.53.202.73',
registered: '2019-03-11'
},
{
id: '19',
first_name: 'Tilly',
last_name: 'Goodin',
email: '[email protected]',
country: 'France',
ip_address: '155.213.172.112',
registered: '2021-05-13'
},
{
id: '20',
first_name: 'Linda',
last_name: 'Lent',
email: '[email protected]',
country: 'Brazil',
ip_address: '148.179.11.167',
registered: '2017-09-24'
},
{
id: '21',
first_name: 'Laina',
last_name: 'Carbry',
email: '[email protected]',
country: 'China',
ip_address: '193.84.239.208',
registered: '2017-05-14'
},
{
id: '22',
first_name: 'Tremayne',
last_name: 'Wilcot',
email: '[email protected]',
country: 'Indonesia',
ip_address: '177.192.189.51',
registered: '2019-05-01'
},
{
id: '23',
first_name: 'Lisha',
last_name: 'Casacchia',
email: '[email protected]',
country: 'Iran',
ip_address: '140.229.23.132',
registered: '2018-05-11'
},
{
id: '24',
first_name: 'Christean',
last_name: 'Donaghy',
email: '[email protected]',
country: 'Indonesia',
ip_address: '194.150.3.107',
registered: '2017-11-09'
},
{
id: '25',
first_name: 'Rabbi',
last_name: 'Commings',
email: '[email protected]',
country: 'China',
ip_address: '170.102.59.120',
registered: '2017-09-14'
},
{
id: '26',
first_name: 'Lazar',
last_name: 'Brightman',
email: '[email protected]',
country: 'China',
ip_address: '96.169.177.183',
registered: '2019-03-12'
},
{
id: '27',
first_name: 'Fara',
last_name: 'Pixton',
email: '[email protected]',
country: 'China',
ip_address: '125.192.244.33',
registered: '2020-07-02'
},
{
id: '28',
first_name: 'Frankie',
last_name: 'Symmers',
email: '[email protected]',
country: 'United Kingdom',
ip_address: '15.95.185.50',
registered: '2017-05-06'
},
{
id: '29',
first_name: 'Sullivan',
last_name: 'Duchant',
email: '[email protected]',
country: 'Thailand',
ip_address: '52.69.63.4',
registered: '2020-09-21'
},
{
id: '30',
first_name: 'Gale',
last_name: 'Yggo',
email: '[email protected]',
country: 'Luxembourg',
ip_address: '10.147.136.158',
registered: '2017-03-01'
},
{
id: '31',
first_name: 'Daphne',
last_name: 'Moscone',
email: '[email protected]',
country: 'Poland',
ip_address: '240.179.82.48',
registered: '2019-01-25'
},
{
id: '32',
first_name: 'Ainslie',
last_name: 'Piperley',
email: '[email protected]',
country: 'China',
ip_address: '212.140.73.147',
registered: '2018-08-16'
},
{
id: '33',
first_name: 'Esme',
last_name: 'Trousdell',
email: '[email protected]',
country: 'Mexico',
ip_address: '120.60.206.157',
registered: '2022-01-25'
},
{
id: '34',
first_name: 'Aurelia',
last_name: 'Salway',
email: '[email protected]',
country: 'China',
ip_address: '126.162.115.255',
registered: '2020-11-11'
},
{
id: '35',
first_name: 'Terry',
last_name: 'McKern',
email: '[email protected]',
country: 'United States',
ip_address: '112.238.5.241',
registered: '2017-08-22'
},
{
id: '36',
first_name: 'Alphonse',
last_name: 'Osgodby',
email: '[email protected]',
country: 'France',
ip_address: '190.137.124.53',
registered: '2019-10-20'
},
{
id: '37',
first_name: 'Boonie',
last_name: 'Gytesham',
email: '[email protected]',
country: 'Germany',
ip_address: '139.137.15.193',
registered: '2017-04-07'
},
{
id: '38',
first_name: 'Robinette',
last_name: 'Denisyuk',
email: '[email protected]',
country: 'Guatemala',
ip_address: '39.59.210.232',
registered: '2021-12-24'
},
{
id: '39',
first_name: 'Kerby',
last_name: 'Walden',
email: '[email protected]',
country: 'China',
ip_address: '52.147.135.77',
registered: '2019-09-04'
},
{
id: '40',
first_name: 'Goldie',
last_name: 'MacMoyer',
email: '[email protected]',
country: 'Greece',
ip_address: '200.8.237.147',
registered: '2019-10-21'
},
{
id: '41',
first_name: 'Clemence',
last_name: 'Tyrie',
email: '[email protected]',
country: 'Sweden',
ip_address: '180.56.118.209',
registered: '2019-06-30'
},
{
id: '42',
first_name: 'Stormy',
last_name: 'Grog',
email: '[email protected]',
country: 'Japan',
ip_address: '80.0.4.237',
registered: '2017-10-11'
},
{
id: '43',
first_name: 'Loutitia',
last_name: 'Andreev',
email: '[email protected]',
country: 'Japan',
ip_address: '105.113.159.240',
registered: '2022-11-19'
},
{
id: '44',
first_name: 'Ashla',
last_name: 'Farrer',
email: '[email protected]',
country: 'Philippines',
ip_address: '108.41.116.114',
registered: '2017-06-30'
},
{
id: '45',
first_name: 'Gaye',
last_name: 'Gwilym',
email: '[email protected]',
country: 'Peru',
ip_address: '108.147.41.3',
registered: '2017-11-02'
},
{
id: '46',
first_name: 'Harley',
last_name: 'Vecard',
email: '[email protected]',
country: 'Russia',
ip_address: '166.123.164.35',
registered: '2021-11-08'
},
{
id: '47',
first_name: 'Chadwick',
last_name: 'Francke',
email: '[email protected]',
country: 'Canada',
ip_address: '106.228.188.3',
registered: '2019-05-16'
},
{
id: '48',
first_name: 'Chrisse',
last_name: 'Watkin',
email: '[email protected]',
country: 'Poland',
ip_address: '104.5.101.18',
registered: '2020-03-23'
},
{
id: '49',
first_name: 'Denyse',
last_name: 'Freeman',
email: '[email protected]',
country: 'China',
ip_address: '57.23.133.66',
registered: '2021-03-23'
},
{
id: '50',
first_name: 'Justine',
last_name: 'Conibere',
email: '[email protected]',
country: 'Jamaica',
ip_address: '98.77.41.3',
registered: '2021-10-05'
},
{
id: '51',
first_name: 'Kalila',
last_name: 'Mongenot',
email: '[email protected]',
country: 'Panama',
ip_address: '110.15.203.86',
registered: '2020-07-31'
},
{
id: '52',
first_name: 'Claresta',
last_name: 'Monahan',
email: '[email protected]',
country: 'Indonesia',
ip_address: '26.246.210.121',
registered: '2021-10-01'
},
{
id: '53',
first_name: 'Lindsey',
last_name: 'Huchot',
email: '[email protected]',
country: 'Azerbaijan',
ip_address: '167.199.9.177',
registered: '2022-05-12'
},
{
id: '54',
first_name: 'Luciano',
last_name: 'Ollerearnshaw',
email: '[email protected]',
country: 'Peru',
ip_address: '232.123.57.3',
registered: '2022-05-18'
},
{
id: '55',
first_name: 'Artie',
last_name: 'Focke',
email: '[email protected]',
country: 'Poland',
ip_address: '141.229.245.46',
registered: '2021-11-06'
},
{
id: '56',
first_name: 'Francyne',
last_name: 'Gravestone',
email: '[email protected]',
country: 'Indonesia',
ip_address: '60.151.142.197',
registered: '2017-07-27'
},
{
id: '57',
first_name: 'Kare',
last_name: 'Mayling',
email: '[email protected]',
country: 'Indonesia',
ip_address: '179.31.176.197',
registered: '2018-11-14'
},
{
id: '58',
first_name: 'Tonya',
last_name: 'Cressor',
email: '[email protected]',
country: 'China',
ip_address: '215.203.39.111',
registered: '2018-06-16'
},
{
id: '59',
first_name: 'Cindee',
last_name: 'Reddington',
email: '[email protected]',
country: 'South Korea',
ip_address: '63.234.142.163',
registered: '2021-07-07'
},
{
id: '60',
first_name: 'Felice',
last_name: 'Sneezum',
email: '[email protected]',
country: 'Indonesia',
ip_address: '225.218.11.88',
registered: '2019-03-31'
},
{
id: '61',
first_name: 'Aluin',
last_name: 'Braben',
email: '[email protected]',
country: 'Japan',
ip_address: '138.199.112.47',
registered: '2017-05-30'
},
{
id: '62',
first_name: 'Gerome',
last_name: 'Rowlinson',
email: '[email protected]',
country: 'Ireland',
ip_address: '205.131.176.187',
registered: '2020-12-05'
},
{
id: '63',
first_name: 'Ly',
last_name: 'Roze',
email: '[email protected]',
country: 'Czech Republic',
ip_address: '53.186.166.82',
registered: '2017-08-22'
},
{
id: '64',
first_name: 'Fan',
last_name: 'Bente',
email: '[email protected]',
country: 'Bulgaria',
ip_address: '208.170.126.232',
registered: '2020-09-06'
},
{
id: '65',
first_name: 'Stephanus',
last_name: 'Deverson',
email: '[email protected]',
country: 'Portugal',
ip_address: '79.162.137.0',
registered: '2019-12-23'
},
{
id: '66',
first_name: 'Julienne',
last_name: 'Brydell',
email: '[email protected]',
country: 'Azerbaijan',
ip_address: '87.32.218.94',
registered: '2019-06-12'
},
{
id: '67',
first_name: 'Brit',
last_name: 'Choules',
email: '[email protected]',
country: 'Portugal',
ip_address: '122.153.96.157',
registered: '2018-01-05'
},
{
id: '68',
first_name: 'Rawley',
last_name: 'Tiebe',
email: '[email protected]',
country: 'Jamaica',
ip_address: '46.39.61.102',
registered: '2019-08-22'
},
{
id: '69',
first_name: 'Morley',
last_name: 'Mellmer',
email: '[email protected]',
country: 'Portugal',
ip_address: '76.235.54.184',
registered: '2019-07-14'
},
{
id: '70',
first_name: 'Reinaldos',
last_name: 'Fernandes',
email: '[email protected]',
country: 'Indonesia',
ip_address: '175.71.20.28',
registered: '2021-03-03'
},
{
id: '71',
first_name: 'Aron',
last_name: 'Marsie',
email: '[email protected]',
country: 'Philippines',
ip_address: '163.5.128.113',
registered: '2021-12-12'
},
{
id: '72',
first_name: 'Mignon',
last_name: 'MacLeod',
email: '[email protected]',
country: 'Peru',
ip_address: '73.76.76.203',
registered: '2018-12-28'
},
{
id: '73',
first_name: 'Jobina',
last_name: 'Antonijevic',
email: '[email protected]',
country: 'Sweden',
ip_address: '235.102.55.226',
registered: '2021-08-17'
},
{
id: '74',
first_name: 'Fredric',
last_name: 'Tuke',
email: '[email protected]',
country: 'Angola',
ip_address: '224.210.197.236',
registered: '2019-11-06'
},
{
id: '75',
first_name: 'Ron',
last_name: 'Cacacie',
email: '[email protected]',
country: 'Philippines',
ip_address: '219.250.115.66',
registered: '2020-06-01'
},
{
id: '76',
first_name: 'Natassia',
last_name: 'Whisker',
email: '[email protected]',
country: 'Brazil',
ip_address: '129.209.182.96',
registered: '2019-07-06'
},
{
id: '77',
first_name: 'Laurena',
last_name: 'Yemm',
email: '[email protected]',
country: 'Ukraine',
ip_address: '234.36.49.108',
registered: '2019-04-26'
},
{
id: '78',
first_name: 'Brett',
last_name: 'Bundey',
email: '[email protected]',
country: 'China',
ip_address: '147.107.165.124',
registered: '2017-03-15'
},
{
id: '79',
first_name: 'Gwendolyn',
last_name: 'Aleevy',
email: '[email protected]',
country: 'Indonesia',
ip_address: '236.170.159.101',
registered: '2021-11-24'
},
{
id: '80',
first_name: 'Marice',
last_name: 'Corston',
email: '[email protected]',
country: 'Guadeloupe',
ip_address: '137.95.107.239',
registered: '2022-10-06'
},
{
id: '81',
first_name: 'Porty',
last_name: 'Pembry',
email: '[email protected]',
country: 'China',
ip_address: '124.228.125.51',
registered: '2019-12-03'
},
{
id: '82',
first_name: 'Kenyon',
last_name: 'Banker',
email: '[email protected]',
country: 'Russia',
ip_address: '45.23.186.203',
registered: '2019-08-12'
},
{
id: '83',
first_name: 'Friedrich',
last_name: 'Phettis',
email: '[email protected]',
country: 'Comoros',
ip_address: '123.55.170.241',
registered: '2018-12-03'
},
{
id: '84',
first_name: 'Dennie',
last_name: 'Rickwood',
email: '[email protected]',
country: 'Vietnam',
ip_address: '195.208.216.82',
registered: '2020-05-07'
},
{
id: '85',
first_name: 'Stafford',
last_name: 'Jendrich',
email: '[email protected]',
country: 'China',
ip_address: '127.23.207.10',
registered: '2019-11-16'
},
{
id: '86',
first_name: 'Weston',
last_name: 'Westcarr',
email: '[email protected]',
country: 'Peru',
ip_address: '244.140.106.99',
registered: '2018-01-08'
},
{
id: '87',
first_name: 'Karole',
last_name: 'Cumberledge',
email: '[email protected]',
country: 'Macedonia',
ip_address: '5.178.26.46',
registered: '2017-06-15'
},
{
id: '88',
first_name: 'Fanchon',
last_name: 'Alenshev',
email: '[email protected]',
country: 'Indonesia',
ip_address: '135.228.36.1',
registered: '2018-11-17'
},
{
id: '89',
first_name: 'Kele',
last_name: 'Beech',
email: '[email protected]',
country: 'Russia',
ip_address: '157.92.244.151',
registered: '2020-10-19'
},
{
id: '90',
first_name: 'Crystal',
last_name: 'Westgate',
email: '[email protected]',
country: 'Greece',
ip_address: '69.12.109.157',
registered: '2019-05-04'
},
{
id: '91',
first_name: 'Robinet',
last_name: 'Pargent',
email: '[email protected]',
country: 'Indonesia',
ip_address: '105.176.209.246',
registered: '2020-11-06'
},
{
id: '92',
first_name: 'Ritchie',
last_name: 'Dealey',
email: '[email protected]',
country: 'Tanzania',
ip_address: '11.217.187.232',
registered: '2021-07-07'
},
{
id: '93',
first_name: 'Karyl',
last_name: 'Bischop',
email: '[email protected]',
country: 'Thailand',
ip_address: '220.178.23.247',
registered: '2020-03-31'
},
{
id: '94',
first_name: 'Lemmie',
last_name: 'Tatterton',
email: '[email protected]',
country: 'Indonesia',
ip_address: '252.191.234.163',
registered: '2022-04-13'
},
{
id: '95',
first_name: 'Ladonna',
last_name: 'Deverille',
email: '[email protected]',
country: 'Dominican Republic',
ip_address: '25.248.243.242',
registered: '2018-01-27'
},
{
id: '96',
first_name: 'Austin',
last_name: 'Girk',
email: '[email protected]',
country: 'China',
ip_address: '180.82.151.113',
registered: '2020-05-24'
},
{
id: '97',
first_name: 'Letisha',
last_name: 'Groveham',
email: '[email protected]',
country: 'Sweden',
ip_address: '95.159.112.187',
registered: '2019-06-23'
},
{
id: '98',
first_name: 'Leonerd',
last_name: 'Hollingby',
email: '[email protected]',
country: 'Indonesia',
ip_address: '109.27.62.206',
registered: '2018-04-13'
},
{
id: '99',
first_name: 'Ally',
last_name: 'Chadwin',
email: '[email protected]',
country: 'Lebanon',
ip_address: '168.77.72.236',
registered: '2022-07-15'
}
];
}Custom search
Pass SearchFn callback to the search prop for search customization.
import { AsyncPipe, JsonPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { cilPaperclip } from '@coreui/icons';
import { IconDirective } from '@coreui/icons-angular';
import { IOption, MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
import { OptionsService } from './options.service';
@Component({
selector: 'docs-multi-select-custom-search',
templateUrl: './multi-select-custom-search.component.html',
providers: [OptionsService],
imports: [ReactiveFormsModule, MultiSelectComponent, MultiSelectOptionComponent, IconDirective, AsyncPipe, JsonPipe]
})
export class MultiSelectCustomSearchComponent {
icons = { cilPaperclip };
options;
readonly options$ = new BehaviorSubject<any[]>([]);
readonly formGroup = new FormGroup({
sampleSelect: new FormControl<string[]>(['4'])
});
searchFn = (option: IOption, searchValue: string): boolean =>
option.label?.toLowerCase().startsWith(searchValue.trimStart().toLowerCase()) ?? true;
constructor(private optionsService: OptionsService) {
this.options = optionsService.users.map((option) => ({
value: option.id,
label: option.last_name
}));
this.options$.next([...this.options]);
}
}<form [formGroup]="formGroup">
<p>Form value: {{ formGroup.value | json }}</p>
<hr />
<c-multi-select
[search]="searchFn"
formControlName="sampleSelect"
multiple="true"
visibleItems="8"
>
@for (option of options$ | async; track option.value) {
<c-multi-select-option [value]="option.value">
<svg [cIcon]="icons.cilPaperclip" class="me-1" />
{{ option.label }}
</c-multi-select-option>
}
</c-multi-select>
</form>Virtual scroller
Display large selection lists in a performant way by only rendering the options in view.
import { AsyncPipe, JsonPipe } from '@angular/common';
import { Component, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { BehaviorSubject } from 'rxjs';
import { map, take, tap } from 'rxjs/operators';
import { IOption, MultiSelectComponent } from '@coreui/angular';
import { OptionsRemoteService } from './optionsRemote.service';
@Component({
selector: 'docs-multi-select-virtual-scroller',
templateUrl: './multi-select-virtual-scroller.component.html',
providers: [OptionsRemoteService],
imports: [ReactiveFormsModule, JsonPipe, AsyncPipe, MultiSelectComponent]
})
export class MultiSelectVirtualScrollerComponent {
readonly #optionsService = inject(OptionsRemoteService);
readonly searchValue$ = new BehaviorSubject<string>('');
readonly loading = signal(true);
readonly options$ = this.#optionsService.search(this.searchValue$).pipe(
tap(() => {
this.loading.set(true);
}),
take(1),
map((next) => {
return next.map((option) => {
const value = option.id.toString().trim();
const label = option.last_name;
const text = `${option.last_name} [${value}]`;
const disabled = option.id === '6';
return { value, label, text, disabled };
});
}),
tap(() => {
this.loading.set(false);
})
);
readonly formGroup = new FormGroup({
sampleSelect: new FormControl<string[]>(['6'])
});
searchFn = (option: IOption, searchValue: string): boolean =>
option.label?.toLowerCase().startsWith(searchValue.trimStart().toLowerCase()) ?? true;
}<form [formGroup]="formGroup">
<p>Form value: {{ formGroup.value | json }}</p>
<hr />
<c-multi-select
(searchValueChange)="searchValue$.next($event)"
[loading]="loading()"
[options]="(options$ | async) ?? []"
[search]="searchFn"
formControlName="sampleSelect"
itemMinWidth="333"
multiple
virtualScroller
visibleItems="8"
/>
</form>import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, Observable, retry, Subject, switchMap, tap, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
const httpOptions = {
// headers: new HttpHeaders({
// 'Content-Type': 'application/json',
// 'Access-Control-Allow-Origin': '*',
// Connection: 'keep-alive'
// })
};
export interface IData {
number_of_records: number;
number_of_matching_records: number;
records: IUsers[];
}
export interface IUsers {
id: string;
first_name: string;
last_name: string;
email: string;
country: string;
ip_address: string;
registered: string;
}
export interface IApiParams {
offset?: number;
limit?: number;
columnFilter?: string;
columnSorter?: string;
sort?: string;
}
@Injectable()
export class OptionsRemoteService {
constructor(
private httpClient: HttpClient
) {
}
private readonly usersUrl = 'https://apitest.coreui.io/demos/users';
/** GET options from the server */
search(name$: Subject<string>): Observable<IUsers[]> {
return name$.pipe(
debounceTime(100),
distinctUntilChanged(),
switchMap((name) => {
const params = { last_name: name, offset: 0, limit: 15000, sort: 'id%asc' };
return this.fetchData(params);
})
);
}
private fetchData(params: IApiParams): Observable<IUsers[]> {
const apiParams = {
...params
};
const httpParams: HttpParams = new HttpParams({ fromObject: apiParams });
const options = Object.keys(httpParams).length
? { params: httpParams, ...httpOptions }
: { params: {}, ...httpOptions };
return this.httpClient
.get<IData>(this.usersUrl, options)
.pipe(
retry({ count: 1, delay: 1000, resetOnSuccess: true }),
catchError(this.handleHttpError),
tap(response => {
console.log('httpClient', response);
}),
map(response => response.records)
);
}
private handleHttpError(error: HttpErrorResponse) {
return throwError(() => error);
}
}Virtual scroller with ng-template and external search.
You can pass an ng-template with cTemplateId="multiSelectOptionTemplate" as a c-multi-select content.
To use multiSelectOptionTemplate template you have to:
import {SharedModule} from '@coreui/angular';- pass it as a string to
[cTemplateId]directive
import { Component, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { AsyncPipe, JsonPipe, NgStyle } from '@angular/common';
import { BehaviorSubject } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { MultiSelectComponent, MultiSelectOptionComponent, TemplateIdDirective } from '@coreui/angular';
import { IconDirective } from '@coreui/icons-angular';
import { cilFolderOpen, cilPaperclip } from '@coreui/icons';
import { OptionsRemoteService } from './optionsRemote.service';
@Component({
selector: 'docs-multi-select-virtual-scroller-2',
templateUrl: './multi-select-virtual-scroller-2.component.html',
providers: [OptionsRemoteService],
imports: [
AsyncPipe,
IconDirective,
JsonPipe,
MultiSelectComponent,
MultiSelectOptionComponent,
NgStyle,
ReactiveFormsModule,
TemplateIdDirective
]
})
export class MultiSelectVirtualScroller2Component {
readonly #optionsService = inject(OptionsRemoteService);
readonly icons = { cilPaperclip, cilFolderOpen };
readonly searchValue$ = new BehaviorSubject<string>('');
readonly loading = signal(true);
readonly formGroup = new FormGroup({
sampleSelect: new FormControl<number[]>([200])
});
readonly options$ = this.#optionsService.search(this.searchValue$).pipe(
tap(() => {
this.loading.set(true);
}),
map((next) => {
return next.map((option) => {
const value = parseInt(option.id);
const label = option.last_name;
const text = `${option.last_name} [${value}]`;
return { value, label, text };
});
}),
tap(() => {
this.loading.set(false);
})
);
handleValueChange($event: any) {
console.log('handleValueChange', $event);
}
}<form [formGroup]="formGroup">
<p>Form value: {{ formGroup.value | json }}</p>
<hr>
<c-multi-select
(searchValueChange)="searchValue$.next($event)"
(valueChange)="handleValueChange($event)"
[loading]="loading()"
[multiple]="true"
[options]="(options$ | async) ?? []"
[search]="'external'"
[visibleItems]="8"
formControlName="sampleSelect"
itemMinWidth="333"
virtualScroller
>
<ng-template cTemplateId="multiSelectOptionTemplate" let-even="even" let-index="index" let-option>
<c-multi-select-option
[disabled]="option.disabled"
[label]="option.label"
[ngStyle]="{'background-color': even ? '#ffeeee' : '#eeffee' }"
[text]="option.text"
[value]="option.value"
[visible]="option.visible"
>
<svg [cIcon]="even ? icons.cilPaperclip : icons.cilFolderOpen" class="me-1" />
<b>{{option.value}}.</b> {{option.label}} [idx: {{index}}]
</c-multi-select-option>
</ng-template>
</c-multi-select>
</form>import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, Observable, retry, Subject, switchMap, tap, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
const httpOptions = {
// headers: new HttpHeaders({
// 'Content-Type': 'application/json',
// 'Access-Control-Allow-Origin': '*',
// Connection: 'keep-alive'
// })
};
export interface IData {
number_of_records: number;
number_of_matching_records: number;
records: IUsers[];
}
export interface IUsers {
id: string;
first_name: string;
last_name: string;
email: string;
country: string;
ip_address: string;
registered: string;
}
export interface IApiParams {
offset?: number;
limit?: number;
columnFilter?: string;
columnSorter?: string;
sort?: string;
}
@Injectable()
export class OptionsRemoteService {
constructor(
private httpClient: HttpClient
) {
}
private readonly usersUrl = 'https://apitest.coreui.io/demos/users';
/** GET options from the server */
search(name$: Subject<string>): Observable<IUsers[]> {
return name$.pipe(
debounceTime(100),
distinctUntilChanged(),
switchMap((name) => {
const params = { last_name: name, offset: 0, limit: 15000, sort: 'id%asc' };
return this.fetchData(params);
})
);
}
private fetchData(params: IApiParams): Observable<IUsers[]> {
const apiParams = {
...params
};
const httpParams: HttpParams = new HttpParams({ fromObject: apiParams });
const options = Object.keys(httpParams).length
? { params: httpParams, ...httpOptions }
: { params: {}, ...httpOptions };
return this.httpClient
.get<IData>(this.usersUrl, options)
.pipe(
retry({ count: 1, delay: 1000, resetOnSuccess: true }),
catchError(this.handleHttpError),
tap(response => {
console.log('httpClient', response);
}),
map(response => response.records)
);
}
private handleHttpError(error: HttpErrorResponse) {
return throwError(() => error);
}
}Forms
CoreUI MultiSelect component works with native html form method as is.
Angular handles user input through reactive and template-driven forms. CoreUI MultiSelect supports both approaches.
If no value prop is included for c-multi-select-option, the value defaults to the text contained inside the
element.
Reactive
import { JsonPipe } from '@angular/common';
import { Component } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-reactive',
templateUrl: './multi-select-reactive.component.html',
imports: [ReactiveFormsModule, MultiSelectComponent, MultiSelectOptionComponent, JsonPipe]
})
export class MultiSelectReactiveComponent {
readonly formGroup = new FormGroup({
multiSelect: new FormControl(['Angular', 'Bootstrap'])
});
}<form [formGroup]="formGroup">
<c-multi-select formControlName="multiSelect" multiple>
<c-multi-select-option>Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option>React.js</c-multi-select-option>
<c-multi-select-option>Vue.js</c-multi-select-option>
</c-multi-select>
</form>
<br>
<p> Form value: {{ formGroup.value | json }}</p>Template-driven
import { Component, signal } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MultiSelectComponent, MultiSelectOptionComponent } from '@coreui/angular';
@Component({
selector: 'docs-multi-select-template-driven',
templateUrl: './multi-select-template-driven.component.html',
imports: [ReactiveFormsModule, FormsModule, MultiSelectComponent, MultiSelectOptionComponent, JsonPipe]
})
export class MultiSelectTemplateDrivenComponent {
readonly value = signal<string[]>([]);
}<form #form="ngForm">
<c-multi-select multiple name="multiSelect" [(ngModel)]="value">
<c-multi-select-option [selected]="true">Angular</c-multi-select-option>
<c-multi-select-option>Bootstrap</c-multi-select-option>
<c-multi-select-option value="react" [selected]="true">React.js</c-multi-select-option>
<c-multi-select-option value="vue">Vue.js</c-multi-select-option>
</c-multi-select>
</form>
<br>
<p> Form value: {{ form.value | json }}</p>
<p> value: {{ value() | json }}</p>Customizing
CSS variables
Angular multi selects use local CSS variables on .form-multi-select for enhanced real-time customization. Values for
the CSS variables are set via Sass, so Sass customization is still supported, too.
--#{$prefix}form-multi-select-zindex: #{$form-multi-select-zindex};
--#{$prefix}form-multi-select-font-family: #{$form-multi-select-font-family};
--#{$prefix}form-multi-select-font-size: #{$form-multi-select-font-size};
--#{$prefix}form-multi-select-font-weight: #{$form-multi-select-font-weight};
--#{$prefix}form-multi-select-line-height: #{$form-multi-select-line-height};
--#{$prefix}form-multi-select-color: #{$form-multi-select-color};
--#{$prefix}form-multi-select-bg: #{$form-multi-select-bg};
--#{$prefix}form-multi-select-box-shadow: #{$form-multi-select-box-shadow};
--#{$prefix}form-multi-select-border-width: #{$form-multi-select-border-width};
--#{$prefix}form-multi-select-border-color: #{$form-multi-select-border-color};
--#{$prefix}form-multi-select-border-radius: #{$form-multi-select-border-radius};
--#{$prefix}form-multi-select-disabled-color: #{$form-multi-select-disabled-color};
--#{$prefix}form-multi-select-disabled-bg: #{$form-multi-select-disabled-bg};
--#{$prefix}form-multi-select-disabled-border-color: #{$form-multi-select-disabled-border-color};
--#{$prefix}form-multi-select-focus-color: #{$form-multi-select-focus-color};
--#{$prefix}form-multi-select-focus-bg: #{$form-multi-select-focus-bg};
--#{$prefix}form-multi-select-focus-border-color: #{$form-multi-select-focus-border-color};
--#{$prefix}form-multi-select-focus-box-shadow: #{$form-multi-select-focus-box-shadow};
--#{$prefix}form-multi-select-placeholder-color: #{$form-multi-select-placeholder-color};
--#{$prefix}form-multi-select-selection-padding-y: #{$form-multi-select-selection-padding-y};
--#{$prefix}form-multi-select-selection-padding-x: #{$form-multi-select-selection-padding-x};
--#{$prefix}form-multi-select-cleaner-width: #{$form-multi-select-cleaner-width};
--#{$prefix}form-multi-select-cleaner-height: #{$form-multi-select-cleaner-height};
--#{$prefix}form-multi-select-cleaner-padding-y: #{$form-multi-select-cleaner-padding-y};
--#{$prefix}form-multi-select-cleaner-padding-x: #{$form-multi-select-cleaner-padding-x};
--#{$prefix}form-multi-select-cleaner-icon: #{escape-svg($form-multi-select-cleaner-icon)};
--#{$prefix}form-multi-select-cleaner-icon-color: #{$form-multi-select-cleaner-icon-color};
--#{$prefix}form-multi-select-cleaner-icon-hover-color: #{$form-multi-select-cleaner-icon-hover-color};
--#{$prefix}form-multi-select-cleaner-icon-size: #{$form-multi-select-cleaner-icon-size};
--#{$prefix}form-multi-select-indicator-width: #{$form-multi-select-indicator-width};
--#{$prefix}form-multi-select-indicator-height: #{$form-multi-select-indicator-height};
--#{$prefix}form-multi-select-indicator-padding-y: #{$form-multi-select-indicator-padding-y};
--#{$prefix}form-multi-select-indicator-padding-x: #{$form-multi-select-indicator-padding-x};
--#{$prefix}form-multi-select-indicator-icon: #{escape-svg($form-multi-select-indicator-icon)};
--#{$prefix}form-multi-select-indicator-icon-color: #{$form-multi-select-indicator-icon-color};
--#{$prefix}form-multi-select-indicator-icon-hover-color: #{$form-multi-select-indicator-icon-hover-color};
--#{$prefix}form-multi-select-indicator-icon-size: #{$form-multi-select-indicator-icon-size};
--#{$prefix}form-multi-select-select-all-padding-y: #{$form-multi-select-select-all-padding-y};
--#{$prefix}form-multi-select-select-all-padding-x: #{$form-multi-select-select-all-padding-x};
--#{$prefix}form-multi-select-select-all-color: #{$form-multi-select-select-all-color};
--#{$prefix}form-multi-select-select-all-bg: #{$form-multi-select-select-all-bg};
--#{$prefix}form-multi-select-select-all-border-width: #{$form-multi-select-select-all-border-width};
--#{$prefix}form-multi-select-select-all-border-color: #{$form-multi-select-select-all-border-color};
--#{$prefix}form-multi-select-select-all-hover-color: #{$form-multi-select-select-all-hover-color};
--#{$prefix}form-multi-select-select-all-hover-bg: #{$form-multi-select-select-all-hover-bg};
--#{$prefix}form-multi-select-dropdown-min-width: #{$form-multi-select-dropdown-min-width};
--#{$prefix}form-multi-select-dropdown-bg: #{$form-multi-select-dropdown-bg};
--#{$prefix}form-multi-select-dropdown-border-width: #{$form-multi-select-dropdown-border-width};
--#{$prefix}form-multi-select-dropdown-border-color: #{$form-multi-select-dropdown-border-color};
--#{$prefix}form-multi-select-dropdown-border-radius: #{$form-multi-select-dropdown-border-radius};
--#{$prefix}form-multi-select-dropdown-box-shadow: #{$form-multi-select-dropdown-box-shadow};
--#{$prefix}form-multi-select-options-padding-y: #{$form-multi-select-options-padding-y};
--#{$prefix}form-multi-select-options-padding-x: #{$form-multi-select-options-padding-x};
--#{$prefix}form-multi-select-options-font-size: #{$form-multi-select-options-font-size};
--#{$prefix}form-multi-select-options-font-weight: #{$form-multi-select-options-font-weight};
--#{$prefix}form-multi-select-options-color: #{$form-multi-select-options-color};
--#{$prefix}form-multi-select-optgroup-label-padding-y: #{$form-multi-select-optgroup-label-padding-y};
--#{$prefix}form-multi-select-optgroup-label-padding-x: #{$form-multi-select-optgroup-label-padding-x};
--#{$prefix}form-multi-select-optgroup-label-font-size: #{$form-multi-select-optgroup-label-font-size};
--#{$prefix}form-multi-select-optgroup-label-font-weight: #{$form-multi-select-optgroup-label-font-weight};
--#{$prefix}form-multi-select-optgroup-label-color: #{$form-multi-select-optgroup-label-color};
--#{$prefix}form-multi-select-optgroup-label-text-transform: #{$form-multi-select-optgroup-label-text-transform};
--#{$prefix}form-multi-select-option-padding-y: #{$form-multi-select-option-padding-y};
--#{$prefix}form-multi-select-option-padding-x: #{$form-multi-select-option-padding-x};
--#{$prefix}form-multi-select-option-margin-y: #{$form-multi-select-option-margin-y};
--#{$prefix}form-multi-select-option-margin-x: #{$form-multi-select-option-margin-x};
--#{$prefix}form-multi-select-option-border-width: #{$form-multi-select-option-border-width};
--#{$prefix}form-multi-select-option-border-color: #{$form-multi-select-option-border-color};
--#{$prefix}form-multi-select-option-border-radius: #{$form-multi-select-option-border-radius};
--#{$prefix}form-multi-select-option-box-shadow: #{$form-multi-select-option-box-shadow};
--#{$prefix}form-multi-select-option-hover-color: #{$form-multi-select-option-hover-color};
--#{$prefix}form-multi-select-option-hover-bg: #{$form-multi-select-option-hover-bg};
--#{$prefix}form-multi-select-option-focus-box-shadow: #{$form-multi-select-option-focus-box-shadow};
--#{$prefix}form-multi-select-option-disabled-color: #{$form-multi-select-option-disabled-color};
--#{$prefix}form-multi-select-option-indicator-width: #{$form-multi-select-option-indicator-width};
--#{$prefix}form-multi-select-option-indicator-bg: #{$form-multi-select-option-indicator-bg};
--#{$prefix}form-multi-select-option-indicator-border: #{$form-multi-select-option-indicator-border};
--#{$prefix}form-multi-select-option-indicator-border-radius: #{$form-multi-select-option-indicator-border-radius};
--#{$prefix}form-multi-select-option-selected-bg: #{$form-multi-select-option-selected-bg};
--#{$prefix}form-multi-select-option-selected-indicator-bg: #{$form-multi-select-option-selected-indicator-bg};
--#{$prefix}form-multi-select-option-selected-indicator-bg-image: #{escape-svg($form-multi-select-option-selected-indicator-bg-image)};
--#{$prefix}form-multi-select-option-selected-indicator-border-color: #{$form-multi-select-option-selected-indicator-border-color};
--#{$prefix}form-multi-select-tag-padding-y: #{$form-multi-select-tag-padding-y};
--#{$prefix}form-multi-select-tag-padding-x: #{$form-multi-select-tag-padding-x};
--#{$prefix}form-multi-select-tag-bg: #{$form-multi-select-tag-bg};
--#{$prefix}form-multi-select-tag-border-width: #{$form-multi-select-tag-border-width};
--#{$prefix}form-multi-select-tag-border-color: #{$form-multi-select-tag-border-color};
--#{$prefix}form-multi-select-tag-border-radius: #{$form-multi-select-tag-border-radius};
--#{$prefix}form-multi-select-tag-delete-width: #{$form-multi-select-tag-delete-width};
--#{$prefix}form-multi-select-tag-delete-height: #{$form-multi-select-tag-delete-height};
--#{$prefix}form-multi-select-tag-delete-icon: #{escape-svg($form-multi-select-tag-delete-icon)};
--#{$prefix}form-multi-select-tag-delete-icon-color: #{$form-multi-select-tag-delete-icon-color};
--#{$prefix}form-multi-select-tag-delete-icon-hover-color: #{$form-multi-select-tag-delete-icon-hover-color};
--#{$prefix}form-multi-select-tag-delete-icon-size: #{$form-multi-select-tag-delete-icon-size};
--#{$prefix}form-multi-select-selection-tags-gap: #{$form-multi-select-selection-tags-gap};
--#{$prefix}form-multi-select-selection-tags-padding-y: #{$form-multi-select-selection-tags-padding-y};
--#{$prefix}form-multi-select-selection-tags-padding-x: #{$form-multi-select-selection-tags-padding-x};How to use CSS variables
const vars = {
'--my-css-var': 10,
'--my-another-css-var': "red"
}<div [ngStyle]="vars"></div>SASS variables
$form-multi-select-zindex: 1000;
$form-multi-select-font-family: $input-font-family;
$form-multi-select-font-size: $input-font-size;
$form-multi-select-font-weight: $input-font-weight;
$form-multi-select-line-height: $input-line-height;
$form-multi-select-color: $input-color;
$form-multi-select-bg: $input-bg;
$form-multi-select-box-shadow: $box-shadow-inset;
$form-multi-select-border-width: $input-border-width;
$form-multi-select-border-color: $input-border-color;
$form-multi-select-border-radius: $input-border-radius;
$form-multi-select-border-radius-sm: $input-border-radius-sm;
$form-multi-select-border-radius-lg: $input-border-radius-lg;
$form-multi-select-disabled-color: $input-disabled-color;
$form-multi-select-disabled-bg: $input-disabled-bg;
$form-multi-select-disabled-border-color: $input-disabled-border-color;
$form-multi-select-focus-color: $input-focus-color;
$form-multi-select-focus-bg: $input-focus-bg;
$form-multi-select-focus-border-color: $input-focus-border-color;
$form-multi-select-focus-box-shadow: $input-btn-focus-box-shadow;
$form-multi-select-invalid-border-color: var(--#{$prefix}form-invalid-border-color);
$form-multi-select-valid-border-color: var(--#{$prefix}form-valid-border-color);
$form-multi-select-placeholder-color: var(--#{$prefix}secondary-color);
$form-multi-select-selection-padding-y: $input-padding-y;
$form-multi-select-selection-padding-x: $input-padding-x;
$form-multi-select-selection-tags-gap: .25rem;
$form-multi-select-selection-tags-padding-y: .25rem;
$form-multi-select-selection-tags-padding-x: .25rem;
$form-multi-select-tag-bg: var(--#{$prefix}secondary-bg);
$form-multi-select-tag-border-width: var(--#{$prefix}border-width);
$form-multi-select-tag-border-color: var(--#{$prefix}border-color);
$form-multi-select-tag-border-radius: .25rem;
$form-multi-select-tag-border-radius-sm: .125rem;
$form-multi-select-tag-border-radius-lg: .375rem;
$form-multi-select-tag-padding-y: .0625rem;
$form-multi-select-tag-padding-x: .5rem;
$form-multi-select-tag-delete-width: .75rem;
$form-multi-select-tag-delete-height: .75rem;
$form-multi-select-tag-delete-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#000'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>");
$form-multi-select-tag-delete-icon-color: var(--#{$prefix}secondary-color);
$form-multi-select-tag-delete-icon-hover-color: var(--#{$prefix}body-color);
$form-multi-select-tag-delete-icon-size: .5rem;
$form-multi-select-cleaner-width: 1.5rem;
$form-multi-select-cleaner-height: 1.5rem;
$form-multi-select-cleaner-padding-x: 0;
$form-multi-select-cleaner-padding-y: 0;
$form-multi-select-cleaner-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#000'><path d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/></svg>");
$form-multi-select-cleaner-icon-color: var(--#{$prefix}tertiary-color);
$form-multi-select-cleaner-icon-hover-color: var(--#{$prefix}body-color);
$form-multi-select-cleaner-icon-size: .625rem;
$form-multi-select-indicator-width: 1.5rem;
$form-multi-select-indicator-height: 1.5rem;
$form-multi-select-indicator-padding-x: 0;
$form-multi-select-indicator-padding-y: 0;
$form-multi-select-indicator-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' fill='#000'><path d='M256.045 416.136.717 160.807l29.579-29.579 225.749 225.748 225.749-225.748 29.579 29.579-255.328 255.329z'/></svg>");
$form-multi-select-indicator-icon-color: var(--#{$prefix}tertiary-color);
$form-multi-select-indicator-icon-hover-color: var(--#{$prefix}body-color);
$form-multi-select-indicator-icon-size: .75rem;
$form-multi-select-dropdown-min-width: 100%;
$form-multi-select-dropdown-bg: var(--#{$prefix}body-bg);
$form-multi-select-dropdown-border-color: var(--#{$prefix}border-color);
$form-multi-select-dropdown-border-width: var(--#{$prefix}border-width);
$form-multi-select-dropdown-border-radius: var(--#{$prefix}border-radius);
$form-multi-select-dropdown-box-shadow: var(--#{$prefix}box-shadow);
$form-multi-select-select-all-padding-y: .5rem;
$form-multi-select-select-all-padding-x: .75rem;
$form-multi-select-select-all-color: var(--#{$prefix}body-secondary-color);
$form-multi-select-select-all-bg: transparent;
$form-multi-select-select-all-hover-color: var(--#{$prefix}body-color);
$form-multi-select-select-all-hover-bg: transparent;
$form-multi-select-select-all-border-width: $input-border-width;
$form-multi-select-select-all-border-color: $input-border-color;
$form-multi-select-options-padding-y: .5rem;
$form-multi-select-options-padding-x: .75rem;
$form-multi-select-options-font-size: $font-size-base;
$form-multi-select-options-font-weight: $font-weight-normal;
$form-multi-select-options-color: var(--#{$prefix}body-color);
$form-multi-select-optgroup-label-padding-y: .5rem;
$form-multi-select-optgroup-label-padding-x: .625rem;
$form-multi-select-optgroup-label-font-size: 80%;
$form-multi-select-optgroup-label-font-weight: $font-weight-bold;
$form-multi-select-optgroup-label-color: var(--#{$prefix}tertiary-color);
$form-multi-select-optgroup-label-text-transform: uppercase;
$form-multi-select-option-padding-y: .5rem;
$form-multi-select-option-padding-x: 1.25rem;
$form-multi-select-option-margin-y: 1px;
$form-multi-select-option-margin-x: 0;
$form-multi-select-option-border-width: $input-border-width;
$form-multi-select-option-border-color: transparent;
$form-multi-select-option-border-radius: var(--#{$prefix}border-radius);
$form-multi-select-option-box-shadow: $box-shadow-inset;
$form-multi-select-option-hover-color: var(--#{$prefix}body-color);
$form-multi-select-option-hover-bg: var(--#{$prefix}tertiary-bg);
$form-multi-select-option-focus-box-shadow: $input-btn-focus-box-shadow;
$form-multi-select-option-indicator-width: 1em;
$form-multi-select-option-indicator-bg: $form-check-input-bg;
$form-multi-select-option-indicator-border: $form-check-input-border;
$form-multi-select-option-indicator-border-radius: .25em;
$form-multi-select-option-selected-bg: var(--#{$prefix}secondary-bg);
$form-multi-select-option-selected-indicator-bg: $form-check-input-checked-bg-color;
$form-multi-select-option-selected-indicator-bg-image: $form-check-input-checked-bg-image;
$form-multi-select-option-selected-indicator-border-color: $form-multi-select-option-selected-indicator-bg;
$form-multi-select-option-disabled-color: var(--#{$prefix}secondary-color);
$form-multi-select-font-size-lg: $input-font-size-lg;
$form-multi-select-selection-padding-y-lg: $input-padding-y-lg;
$form-multi-select-selection-padding-x-lg: $input-padding-x-lg;
$form-multi-select-selection-tags-gap-lg: .25rem;
$form-multi-select-selection-tags-padding-y-lg: .25rem;
$form-multi-select-selection-tags-padding-x-lg: .25rem;
$form-multi-select-tag-padding-y-lg: .175rem;
$form-multi-select-tag-padding-x-lg: .5rem;
$form-multi-select-font-size-sm: $input-font-size-sm;
$form-multi-select-selection-padding-y-sm: $input-padding-y-sm;
$form-multi-select-selection-padding-x-sm: $input-padding-x-sm;
$form-multi-select-selection-tags-gap-sm: .125rem;
$form-multi-select-selection-tags-padding-y-sm: .0625rem;
$form-multi-select-selection-tags-padding-x-sm: .125rem;
$form-multi-select-tag-padding-y-sm: .075rem;
$form-multi-select-tag-padding-x-sm: .5rem;API reference
MultiSelect Module
import { MultiSelectModule, SharedModule } from '@coreui/angular';
@NgModule({
imports: [
MultiSelectModule,
SharedModule
]
})
export class AppModule(){}c-multi-select
component
import { MultiSelectComponent } from '@coreui/angular-pro'Props
Events
c-multi-select-option
component
import { MultiSelectOptionComponent } from '@coreui/angular-pro'Props
Events
| Event name |
|---|
| focusChange |
Emits the option when it gains focus.
|
| selectedChange |
Emits option selected change
|
c-multi-select-optgroup
component
import { MultiSelectOptgroupComponent } from '@coreui/angular-pro'