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

Main wrapper component. Renders DropdownStandardComponent by default. When DROPDOWN_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.

Default concrete implementation. Renders a trigger button and a menu list using modern Angular control flow.

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 DropdownComponent renders injected components via NgComponentOutlet (which passes inputs by canonical name), DropdownPresetComponent overrides cssClass as input<string>('') without the class alias. Bind it as [cssClass] when using the <smart-dropdown-preset> selector directly, or just pass class on <smart-dropdown> (the wrapper forwards it).

Abstract base directive for extending custom dropdown implementations.

API

Inputs

InputTypeDefaultDescription
itemsInputSignal<IDropdownItem[]>[]Menu items
triggerLabelInputSignal<string>undefinedTrigger text; projected content is used when it is not set
openModelSignal<boolean>falseTwo-way bindable open/closed state
optionsInputSignal<IDropdownOptions>undefinedDropdown configuration (variant, headerLabel)
classInputSignal<string>''External CSS classes (alias for cssClass)

Outputs

OutputTypeDescription
selectedItem{ itemId: string }Emits when a menu item is selected; closes menu

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.