Select Menu
The <smart-select-menu> component renders a single-select menu with a placeholder, list of items (label/value, optional avatar/secondary text/status/disabled), an optional empty state slot, and a two-way value binding. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract SelectMenuBaseComponent defines the shared API — value (two-way ModelSignal<SelectMenuValue>), disabled, optional ISelectMenuOptions, cssClass (alias class), and a select(next) method that updates value while respecting the disabled state. SelectMenuStandardComponent is a barebones placeholder concrete implementation using a native <select> element. SelectMenuComponent is the public wrapper that renders SelectMenuStandardComponent by default and accepts a custom replacement via SELECT_MENU_STANDARD_COMPONENT_TOKEN.
Usage
<smart-select-menu [options]="options" [disabled]="disabled" [(value)]="plan" />
Components
SelectMenuComponent (<smart-select-menu>)
Main wrapper component. Renders SelectMenuStandardComponent by default. When SELECT_MENU_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.
SelectMenuStandardComponent (<smart-select-menu-standard>)
Barebones placeholder concrete implementation using a native <select>. Renders an outer wrapper <div>, a <div class="select-menu">, and a <select> with one <option> per item; an optional placeholder is rendered as a disabled first option. Disabled items get the disabled attribute on their <option>. The <select> is disabled when disabled() is true. On change, the standard reads the raw string value, looks up the matching item by String(value) === String(item.value) (preserving numeric types), and calls select(). When items is empty and emptyTpl is provided, the empty template is rendered inside <div class="empty">. The external cssClass is applied to the root wrapper. It does not include any Tailwind UI styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.
SelectMenuBaseComponent (abstract)
Abstract base directive for extending custom select menu implementations. Exposes:
value: ModelSignal<SelectMenuValue>(defaultnull) — two-way bindabledisabled: InputSignal<boolean>(defaultfalse)options: InputSignal<ISelectMenuOptions | undefined>cssClass: InputSignal<string>(aliasclass)select(next: SelectMenuValue): void— updatesvalueonly when not disabled
SelectMenuValue is string | number | null.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
value | ModelSignal<SelectMenuValue> | null | Currently selected value (two-way bindable) |
disabled | InputSignal<boolean> | false | Whether the menu is disabled |
options | InputSignal<ISelectMenuOptions | undefined> | - | Optional configuration (items, placeholder, variant, slots) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
ISelectMenuOptions
The default SelectMenuStandardComponent renders value, disabled, placeholder, items and emptyTpl. variant, avatarUrl, iconTpl, secondary, and status are hints/data for custom implementations registered via the token.
SELECT_MENU_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default SelectMenuStandardComponent with a custom implementation. Provide a Type<SelectMenuBaseComponent> to override.
Extending the base class
import {
ChangeDetectionStrategy,
Component,
computed,
input,
signal,
ViewEncapsulation,
} from '@angular/core';
import {
ISelectMenuOptions,
SELECT_MENU_STANDARD_COMPONENT_TOKEN,
SelectMenuBaseComponent,
SelectMenuComponent,
SelectMenuValue,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-select-menu',
template: `
<div [class]="containerClasses()">
<button
type="button"
class="docs-select-menu__trigger"
[disabled]="disabled()"
[attr.aria-expanded]="expanded()"
[attr.aria-label]="options()?.ariaLabel"
(click)="toggle()"
>
{{ currentLabel() ?? options()?.placeholder ?? 'Select' }}
</button>
@if (expanded()) {
<ul class="docs-select-menu__list" role="listbox">
@for (item of items(); track item.value) {
<li
role="option"
class="docs-select-menu__option"
[attr.aria-selected]="value() === item.value"
[attr.aria-disabled]="item.disabled ?? false"
(click)="pick(item.value, item.disabled ?? false)"
>
{{ item.label }}
</li>
}
</ul>
}
</div>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomSelectMenuComponent extends SelectMenuBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class' alias.
override cssClass = input<string>('');
readonly expanded = signal(false);
readonly items = computed(() => this.options()?.items ?? []);
readonly currentLabel = computed(
() => this.items().find((item) => item.value === this.value())?.label,
);
readonly containerClasses = computed(() => {
const classes = ['docs-select-menu'];
const extra = this.cssClass();
if (extra) classes.push(extra);
return classes.join(' ');
});
toggle(): void {
if (this.disabled()) return;
this.expanded.update((open) => !open);
}
pick(next: SelectMenuValue, itemDisabled: boolean): void {
if (itemDisabled) return;
// select() is the base class API: it ignores the call while disabled.
this.select(next);
this.expanded.set(false);
}
}
@Component({
selector: 'docs-select-menu-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SelectMenuComponent],
// The token swaps the standard native select for the custom listbox
// everywhere below this component, so consumers keep writing
// `<smart-select-menu>`.
providers: [
{
provide: SELECT_MENU_STANDARD_COMPONENT_TOKEN,
useValue: CustomSelectMenuComponent,
},
],
// NgComponentOutlet forwards `value` as a plain input, so the initial value
// reaches the custom component but its own selections stay inside it.
template: ` <smart-select-menu [(value)]="selected" [options]="options" /> `,
})
export class SelectMenuCustomExampleComponent {
readonly selected = signal<SelectMenuValue>(null);
readonly options: ISelectMenuOptions = {
placeholder: 'Choose a country',
ariaLabel: 'Country',
items: [
{ value: 'pl', label: 'Poland' },
{ value: 'de', label: 'Germany' },
{ value: 'us', label: 'United States' },
],
};
}
Source
The component lives in packages/shared/angular/src/lib/components/select-menu and is documented for Claude Code by the angular-components-select-menu skill.