Menu

The sidebar is fully config-driven: you describe it as a typed MenuNode[] and pass it to the layout's menuItems input. No template markup, no manual active-state plumbing.

The MenuNode model

MenuNode is a discriminated union of three node types, keyed on type:

TypeRenders asKey fields
'header'Non-interactive section labeltext
'item'Clickable link (leaf)text, route or href, icon, badge, visible
'group'Collapsible submenu (treeview)text, icon, children, visible

Fields

  • text — the label (all node types).
  • route — an Angular route, rendered with RouterLink (items only).
  • href — an external URL, rendered as a plain anchor; pair with target: '_blank' (items only).
  • icon — a Bootstrap Icons class, with or without the bi prefix (e.g. 'bi-speedometer'); items and groups.
  • iconColor — a BootstrapTheme for the icon color.
  • badge / badgeColor — an optional pill (string | number) with a BootstrapTheme color.
  • children — the nested MenuNode[] (groups only).
  • visible — when false, the node is not rendered. Wire it to your auth/role logic to hide items without removing them from the config.

A real menu

import type { MenuNode } from '@adminlte/angular';

export const MENU: MenuNode[] = [
  { type: 'header', text: 'MAIN NAVIGATION' },
  {
    type: 'group',
    text: 'Dashboard',
    icon: 'bi-speedometer',
    children: [
      { type: 'item', text: 'Dashboard v1', route: '/', icon: 'bi-circle' },
      { type: 'item', text: 'Dashboard v2', route: '/dashboard/v2', icon: 'bi-circle' },
      { type: 'item', text: 'Dashboard v3', route: '/dashboard/v3', icon: 'bi-circle' },
    ],
  },
  {
    type: 'group',
    text: 'UI Elements',
    icon: 'bi-tree-fill',
    children: [
      { type: 'item', text: 'General', route: '/ui/general', icon: 'bi-circle' },
      { type: 'item', text: 'Icons', route: '/ui/icons', icon: 'bi-circle' },
      { type: 'item', text: 'Timeline', route: '/ui/timeline', icon: 'bi-circle' },
    ],
  },
  { type: 'item', text: 'Components', route: '/components', icon: 'bi-puzzle', badge: 'New', badgeColor: 'info' },

  { type: 'header', text: 'EXAMPLES' },
  { type: 'item', text: 'Profile', route: '/profile', icon: 'bi-person-badge' },
  { type: 'item', text: 'Calendar', route: '/calendar', icon: 'bi-calendar3' },
  { type: 'item', text: 'Admin only', route: '/admin', icon: 'bi-lock', visible: false },
  { type: 'item', text: 'AdminLTE.io', href: 'https://adminlte.io', icon: 'bi-globe', target: '_blank' },
];

Active-link detection

The sidebar highlights the active item — and auto-opens its parent group — by comparing each item's route against the currentPath input on <lte-dashboard-layout>. Feed that input from the Angular Router so it tracks navigation:

readonly currentPath = toSignal(
  this.router.events.pipe(
    filter((e): e is NavigationEnd => e instanceof NavigationEnd),
    map((e) => e.urlAfterRedirects.split('?')[0]),
    startWith(this.router.url.split('?')[0]),
  ),
  { initialValue: '/' },
);

Pass [accordion]="true" to keep only one group open at a time.

Command palette source

The same menu config powers the ⌘K command palette: the library flattens it into searchable commands (the flattenMenuToCommands utility is also exported if you need it directly). Open the palette with ⌘K / Ctrl+K or the topbar search icon; selecting a result navigates via the Router.

Because the menu is plain typed data, you can build it dynamically — filtering by role, injecting badges from a signal, or composing it from feature modules — and the sidebar, active detection and command palette all stay in sync.


AdminLTE 4 · Angular port Edit on GitHub