Select Picker
A multi-select that shows chips on one line and picks in a searchable checkbox dialog.
import { … } from '@inlaylab/ivorie/extras/select-picker'import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IV_SELECT_PICKER_IMPORTS } from '@inlaylab/ivorie/extras/select-picker';
@Component({
selector: 'demo-select-picker',
imports: [FormsModule, IV_SELECT_PICKER_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="w-72">
<iv-select-picker
[items]="fruits"
[(ngModel)]="selected"
placeholder="Select fruits"
dialogTitle="Select fruits"
searchPlaceholder="Search fruits"
valueColumnHeader="Fruit"
/>
</div>
`,
})
export default class SelectPickerDemo {
readonly fruits = ['Apple', 'Banana', 'Cherry', 'Grape', 'Mango', 'Orange'];
readonly selected = signal<string[]>(['Apple', 'Mango']);
}
Installation
npx ivorie add select-pickerimport { … } from '@inlaylab/ivorie/extras/select-picker'Use it for multi-select from a known list. For free-text tag entry, use the combobox with searchSubmit.
Usage
import { IV_SELECT_PICKER_IMPORTS } from '@inlaylab/ivorie/extras/select-picker';<iv-select-picker
[items]="groups"
[valueFn]="groupName"
[labelFn]="groupName"
[(ngModel)]="selected"
placeholder="Select groups"
/>readonly groupName = (group: Group): string => group.name;IV_SELECT_PICKER_IMPORTS holds only the trigger. Never add IvSelectPickerDialog to an NgModule imports. IvDialog creates it at runtime, and listing it fails the AOT build with NG3004.
Examples
Form control
Value: Editor
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { IV_SELECT_PICKER_IMPORTS } from '@inlaylab/ivorie/extras/select-picker';
@Component({
selector: 'demo-select-picker-form-control',
imports: [ReactiveFormsModule, IV_SELECT_PICKER_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex w-72 flex-col gap-2">
<iv-select-picker [items]="roles" [formControl]="roles$" placeholder="Select roles" ariaLabel="Roles" />
<p class="text-sm text-muted-foreground">Value: {{ roles$.value.join(', ') || 'none' }}</p>
</div>
`,
})
export default class SelectPickerFormControlDemo {
readonly roles = ['Admin', 'Editor', 'Viewer', 'Billing'];
readonly roles$ = new FormControl<string[]>(['Editor'], { nonNullable: true });
}
Description column
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IV_SELECT_PICKER_IMPORTS } from '@inlaylab/ivorie/extras/select-picker';
interface Team {
id: string;
name: string;
about: string;
}
@Component({
selector: 'demo-select-picker-description-column',
imports: [FormsModule, IV_SELECT_PICKER_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="w-72">
<iv-select-picker
[items]="teams"
[valueFn]="id"
[labelFn]="name"
[descriptionFn]="about"
[(ngModel)]="selected"
placeholder="Select teams"
dialogTitle="Select teams"
valueColumnHeader="Team"
descriptionColumnHeader="About"
/>
</div>
`,
})
export default class SelectPickerDescriptionColumnDemo {
readonly teams: Team[] = [
{ id: 'eng', name: 'Engineering', about: 'Builds the product' },
{ id: 'design', name: 'Design', about: 'Shapes the experience' },
{ id: 'support', name: 'Support', about: 'Helps customers' },
];
readonly selected = signal<string[]>(['eng']);
readonly id = (team: Team) => team.id;
readonly name = (team: Team) => team.name;
readonly about = (team: Team) => team.about;
}
Minimum selection
Save stays disabled until one is checked.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IV_SELECT_PICKER_IMPORTS } from '@inlaylab/ivorie/extras/select-picker';
@Component({
selector: 'demo-select-picker-minimum-selection',
imports: [FormsModule, IV_SELECT_PICKER_IMPORTS],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="flex w-72 flex-col gap-2">
<iv-select-picker
[items]="channels"
[(ngModel)]="selected"
[minSelection]="1"
placeholder="Select channels"
dialogDescription="Pick at least one channel."
/>
<p class="text-sm text-muted-foreground">Save stays disabled until one is checked.</p>
</div>
`,
})
export default class SelectPickerMinimumSelectionDemo {
readonly channels = ['Email', 'SMS', 'Push', 'Slack'];
readonly selected = signal<string[]>(['Email']);
}
API
Every part also accepts class.
iv-select-picker
The value is string[]. It works with ngModel and formControlName.
| Name | Type | Default | Description |
|---|---|---|---|
items |
T[] |
[] |
Full option list. Filtering is client-side. |
valueFn |
(item: T) => string |
String(item) |
Item identity. This is what gets stored. |
labelFn |
(item: T) => string |
String(item) |
Display text for chips and the table. |
descriptionFn |
(item: T) => string |
none | Adds a second, searchable table column. |
placeholder |
string |
'Select…' |
Shown when nothing is selected. |
dialogTitle |
string |
'Select items' |
Dialog title. |
dialogDescription |
string |
'' |
Optional dialog sub-header. |
searchPlaceholder |
string |
'Search' |
Placeholder of the search box. |
valueColumnHeader |
string |
'Name' |
Header of the label column. |
descriptionColumnHeader |
string |
'' |
Header of the description column. Needs descriptionFn. |
emptyMessage |
string |
'No data found' |
Shown when no row matches. |
minSelection |
number |
0 |
Save stays disabled below this count. |
disabled |
boolean |
false |
Also set by the form through setDisabledState. |
ariaLabel |
string |
'' |
Set it when there is no <label ivLabel>. |
id |
string | null |
null |
Goes on the inner button, so <label for> works. |
Outputs:
| Name | Payload | Description |
|---|---|---|
selectionChange |
string[] |
Emits the new selection on Save. The form control gets the same value. |
Accessibility
- The whole control is one
<button>witharia-haspopup="dialog"andaria-expanded. - Set
ariaLabel, or link a<label>withid. - The dialog traps focus. Escape acts as Cancel; a backdrop click does nothing (
disableBackdropClose), so an accidental click outside doesn't discard the selection. - The offscreen chip measuring row is
aria-hidden.
Notes
- Assign a new
itemsarray. An in-place push is not seen. - Chips are measured, not guessed. At least one chip always shows. The rest collapse into
+N more. - Cancel discards. The dialog reports back only on Save.
- Save order follows
itemsorder, not click order. - Select-all applies to the filtered rows only.
- A stored value with no matching item still shows as a chip with its raw value.
- It opens as a nested dialog when the host is a dialog.