Dropdown
The <smart-dropdown> component provides a flexible dropdown menu wrapper with an InjectionToken-based extension mechanism. It renders a default DropdownStandardComponent which can be replaced via DROPDOWN_STANDARD_COMPONENT_TOKEN.
Usage
<smart-dropdown
triggerLabel="Options"
[items]="items"
[options]="options"
(selectedItem)="onSelect($event)"
/>
Components
DropdownComponent (<smart-dropdown>)
Main wrapper component. Renders DropdownStandardComponent by default. When DROPDOWN_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.
DropdownStandardComponent (<smart-dropdown-standard>)
Default concrete implementation. Renders a trigger button and a menu list using modern Angular control flow.
DropdownPresetComponent (<smart-dropdown-preset>)
Fully styled variation that extends DropdownBaseComponent and is a drop-in replacement for DropdownStandardComponent. Register it via DROPDOWN_STANDARD_COMPONENT_TOKEN to restyle every <smart-dropdown>, or use the <smart-dropdown-preset> selector directly. Renders a trigger button (with a rotating chevron) and a menu, dispatched through options.variant (default 'simple'): simple, with-dividers (items split into divider-separated groups), with-icons (renders each item.icon string), with-header (a header block from options.headerLabel, items shown with icons), and minimal (borderless ghost trigger). All classes are smart:-prefixed Tailwind with explicit dark: variants. Open/close is driven by the inherited open signal + a (click) toggle and @if — Preline's JS plugin is NOT used, but its visual classes and ARIA (aria-haspopup="menu", aria-expanded) are preserved. The class recipes live in preset/preset-classes.util.ts (getDropdownTriggerClasses, getDropdownMenuClasses, getDropdownChevronClasses).
Because
DropdownComponentrenders injected components viaNgComponentOutlet(which passes inputs by canonical name),DropdownPresetComponentoverridescssClassasinput<string>('')without theclassalias. Bind it as[cssClass]when using the<smart-dropdown-preset>selector directly, or just passclasson<smart-dropdown>(the wrapper forwards it).
DropdownBaseComponent (abstract)
Abstract base directive for extending custom dropdown implementations.
API
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
items | InputSignal<IDropdownItem[]> | [] | Menu items |
triggerLabel | InputSignal<string> | undefined | Trigger text; projected content is used when it is not set |
open | ModelSignal<boolean> | false | Two-way bindable open/closed state |
options | InputSignal<IDropdownOptions> | undefined | Dropdown configuration (variant, headerLabel) |
class | InputSignal<string> | '' | External CSS classes (alias for cssClass) |
Outputs
| Output | Type | Description |
|---|---|---|
selectedItem | { itemId: string } | Emits when a menu item is selected; closes menu |
DROPDOWN_STANDARD_COMPONENT_TOKEN
InjectionToken that allows replacing the default DropdownStandardComponent with a custom implementation. Provide a Type<DropdownBaseComponent> to override.
Extending the base class
import {
ChangeDetectionStrategy,
Component,
input,
ViewEncapsulation,
} from '@angular/core';
import {
DROPDOWN_STANDARD_COMPONENT_TOKEN,
DropdownBaseComponent,
DropdownComponent,
IDropdownItem,
} from '@smartsoft001/angular';
@Component({
selector: 'docs-custom-dropdown',
template: `
<div class="docs-dropdown" [class]="cssClass()">
<button
type="button"
class="docs-dropdown__trigger"
[attr.aria-expanded]="open()"
(click)="toggle()"
>
{{ triggerLabel() }}
</button>
@if (open()) {
<ul class="docs-dropdown__menu" role="menu">
@for (item of items(); track item.id) {
@if (item.divider) {
<li class="docs-dropdown__divider" role="separator"></li>
} @else {
<li role="none">
<button
type="button"
role="menuitem"
class="docs-dropdown__item"
[disabled]="item.disabled"
(click)="selectItem(item.id)"
>
{{ item.label }}
</button>
</li>
}
}
</ul>
}
</div>
`,
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomDropdownComponent extends DropdownBaseComponent {
// NgComponentOutlet passes 'cssClass' by canonical name, not the 'class'
// alias, so a dropdown registered through the token declares it explicitly.
override cssClass = input<string>('');
// toggle(), selectItem() and close() come from DropdownBaseComponent:
// selectItem() emits selectedItem and closes the menu for you.
}
@Component({
selector: 'docs-dropdown-custom-example',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [DropdownComponent],
// The token swaps the standard dropdown for the custom one everywhere below
// this component, so consumers keep writing `<smart-dropdown>`.
providers: [
{
provide: DROPDOWN_STANDARD_COMPONENT_TOKEN,
useValue: CustomDropdownComponent,
},
],
// NgComponentOutlet forwards inputs but not outputs, so `(selectedItem)` on
// the wrapper stays silent once a custom implementation is registered -
// handle the selection inside the custom component instead.
template: `<smart-dropdown [items]="items" triggerLabel="Actions" />`,
})
export class DropdownCustomExampleComponent {
items: IDropdownItem[] = [
{ id: 'newsletter', label: 'Newsletter' },
{ id: 'sep', label: '', divider: true },
{ id: 'downloads', label: 'Downloads' },
];
}
Source
The component lives in packages/shared/angular/src/lib/components/dropdown and is documented for Claude Code by the angular-components-dropdown skill.