Angular Multi Select Component

Multi Select

CoreUI PRO
This component is part of CoreUI PRO – a powerful UI library with over 250 components and 25+ templates, designed to help you build modern, responsive apps faster. Fully compatible with Angular, Bootstrap, React.js, and Vue.js.

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.29

With 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.29

Use 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>

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'
    }
  ];
}

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"
}
&lt;div [ngStyle]="vars"&gt;&lt;/div&gt;

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

jsx
import { MultiSelectComponent } from '@coreui/angular-pro'

Props

PropertyDefaultType
allowCreateOptions4.5.11+falseboolean

Allow users to create options if they are not in the list of options.

ariaCleanerLabel5.7.7+'Clear selection'string

Sets the accessible label (aria-label) for the button that clears the current selection. This label is read by screen readers.

ariaIndicatorLabel5.7.7+'Toggle visibility of options menu'string

Sets the accessible label (aria-label) for the indicator button that toggles the options menu. This label is read by screen readers.

cleanertrueboolean, 'active'

Enables selection cleaner element

clearSearchOnSelect4.5.11+falseboolean

Clear current search on selecting an item

deselectAllLabel5.7.29+'Deselect all'string

Sets the select all button label shown once everything is selected. The button is a toggle: it shows selectAllLabel and selects all options, then shows deselectAllLabel and deselects them.

deselectFilteredLabel5.7.29+'Deselect filtered'string

Sets the deselect filtered button label, used with selectAllMode="filtered".

disabledfalseboolean

Disables multi-select component

itemMinWidth196number

Min width of the options list (in pixels).

itemSize40number

The size of the option item in the list (in pixels).

loading4.5.2+falseboolean

Add loading spinner and reduced opacity.

multiplefalseboolean

Specifies that multiple options can be selected at once

options[]IOption[]

List of option elements

optionsMaxHeight'auto'string, number

Sets maxHeight of options list in px

optionsStyle'checkbox''checkbox', 'text'

Sets option style

placeholder'Select...'string

Specifies a short hint that is visible in the search input

popperOptionsdefaultPopperOptionsPartial<Options>

Optional popper Options object

resetSelectionOnOptionsChangefalseboolean

Resets selection when options are changed When set to true, any previously selected options will be reset whenever the options list undergoes a change. This ensures that outdated selections are not retained when new options are provided.

Enables search input element

searchNoResultsLabel'no items'string

Sets the label for no results when filtering

searchValue''string

Sets initial search string

selectAlltrueboolean

Enables select all button

selectAllLabel'Select all'string

Sets the select all button label

selectAllMode5.7.29+'all''all', 'filtered'

Determines what the select all button operates on: all options ('all') or only the ones matched by the current search ('filtered').

selectAllStyle5.7.29+'checkbox''checkbox', 'text'

Sets the select all button style. With 'checkbox' the button shows a tri-state indicator (none / all / indeterminate).

selectFilteredLabel5.7.29+'Select filtered'string

Sets the select filtered button label, used with selectAllMode="filtered".

selectionLimit5.7.29+undefinednumber

Sets the maximum number of options that can be selected. The select all button stays enabled and selects options up to the limit. Selecting more options is blocked and emits selectionLimitReached.

selectionType'tags''text', 'counter', 'tags'

Selection type

selectionTypeCounterText'item(s) selected'string

Counter selection label value

selectionTypeCounterTextPluralMap{ '=1': 'item selected', 'other': 'items selected' }IPluralMap

Counter selection label plural map for I18nPluralPipe

size-'', 'sm', 'lg'

Size the component small or large.

validundefinedboolean

Toggle visual validation feedback.

value== true,TValue, TValue[]

Initial value of multi-select

virtualScrollerfalseboolean

Enable virtual scroller for options list.

visiblefalseboolean

Toggle the visibility of the dropdown select component.

visibleItems8number

Amount of visible options, if set - overwrites optionsMaxHeight

Events

Event name
searchValueChange

Emits searchValue string for external filtering

  • $event string
selectionLimitReached

Emits when the user tries to select more options than allowed by selectionLimit.

  • $event void
valueChange

Emits valueChange

  • $event TValue | TValue[]
visibleChange

Emits visibleChange

  • $event boolean

c-multi-select-option

component

jsx
import { MultiSelectOptionComponent } from '@coreui/angular-pro'

Props

PropertyDefaultType
disabledfalseboolean

Option disabled.

labelundefinedstring

Option label

optionsStyleundefined'checkbox', 'text'

Option style. When not set, follows the optionsStyle of the parent c-multi-select ('checkbox').

role'option'string

Role for the option element.

selectedfalseboolean

Option selected.

textundefinedstring

Option inner text

valueundefinedstring, number

Option value.

Events

Event name
focusChange

Emits the option when it gains focus.

  • $event MultiSelectOptionComponent
selectedChange

Emits option selected change

  • $event boolean

c-multi-select-optgroup

component

jsx
import { MultiSelectOptgroupComponent } from '@coreui/angular-pro'
PropertyDefaultType
disabledfalseboolean

Disables all options in the group.

label-string

Options group label.