or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

http-client.mdi18n.mdindex.mdlocale.mdmenu.mdpipes.mdsettings.mdui-helpers.mdutilities.md
tile.json

menu.mddocs/

Menu System

Comprehensive menu management system for Angular applications providing hierarchical navigation, dynamic menu updates, URL-based routing integration, and flexible menu item configuration.

Capabilities

MenuService

The core service for managing application menus with reactive updates and URL-based navigation.

/**
 * Menu management service for application navigation
 * Singleton service providing menu data management and navigation utilities
 */
class MenuService {
  /** Observable stream of menu changes */
  readonly change: Observable<Menu[]>;
  /** Current menu data array */
  readonly menus: Menu[];
  /** Whether to strictly control menu open state, default: false */
  openStrictly: boolean;
  
  /** Set menu data and trigger change notifications */
  add(items: Menu[]): void;
  /** Reset menu data, optionally with callback for custom processing */
  resume<T extends Menu = Menu>(callback?: (item: T, parentMenu: T | null, depth?: number) => void): void;
  /** Clear all menu data */
  clear(): void;
  /** Find menu item by various criteria */
  find(options: MenuFindOptions): Menu | null;
  /** Get menu path by URL with optional recursion */
  getPathByUrl(url: string, recursive?: boolean): Menu[];
  /** Get menu item by key */
  getItem(key: string): Menu | null;
  /** Update menu item by key or menu object */
  setItem(key: string | Menu, value: Menu, options?: { emit?: boolean }): void;
  /** Open menu item and update state */
  open(keyOrItem: string | Menu | null, options?: { emit?: boolean }): void;
  /** Toggle menu item open state */
  toggleOpen(keyOrItem: string | Menu | null, options?: { allStatus?: boolean; emit?: boolean }): void;
  /** Open all menu items with optional status */
  openAll(status?: boolean): void;
  /** Returns a default menu link for redirection */
  getDefaultRedirect(opt?: { redirectUrl?: string }): string | null | undefined;
  /** Visit all menu items recursively with callback */
  visit<T extends Menu = Menu>(data: T[], callback: (item: T, parentMenu: T | null, depth?: number) => void): void;
}

interface MenuFindOptions {
  /** Menu key to search for */
  key?: string | null;
  /** URL to search for */
  url?: string | null;
  /** Whether to search recursively, default: false */
  recursive?: boolean | null;
  /** Custom validation callback */
  cb?: ((i: Menu) => boolean | null) | null;
  /** Menu data to search in, defaults to current menu data */
  data?: Menu[] | null;
  /** Whether to ignore hidden items, default: false */
  ignoreHide?: boolean;
  /** Whether to return the last match, default: false */
  last?: boolean;
}

Usage Examples:

import { Component, inject } from "@angular/core";
import { MenuService, Menu } from "@delon/theme";

@Component({
  selector: "app-sidebar",
  template: `
    <ul>
      <li *ngFor="let menu of menuService.menus">
        <a [routerLink]="menu.link">{{ menu.text }}</a>
      </li>
    </ul>
  `
})
export class SidebarComponent {
  menuService = inject(MenuService);

  constructor() {
    // Set menu data
    const menus: Menu[] = [
      {
        text: "Dashboard",
        link: "/dashboard",
        icon: "dashboard"
      },
      {
        text: "Users",
        link: "/users",
        icon: "user",
        children: [
          { text: "User List", link: "/users/list" },
          { text: "Add User", link: "/users/add" }
        ]
      }
    ];
    
    this.menuService.add(menus);
    
    // Listen to menu changes
    this.menuService.change.subscribe(updatedMenus => {
      console.log("Menu updated:", updatedMenus);
    });
  }
  
  openUserMenu() {
    // Open menu programmatically
    this.menuService.open("users");
  }
  
  findCurrentMenu() {
    // Find menu by URL
    const currentMenu = this.menuService.find({ url: "/users/list" });
    if (currentMenu) {
      console.log("Current menu:", currentMenu.text);
    }
  }
}

Menu Interface

Complete menu item configuration interface supporting hierarchical navigation, icons, badges, and external links.

/**
 * Menu item configuration interface
 * Supports hierarchical navigation with flexible properties
 */
interface Menu {
  /** Rendering type of menu item */
  render_type?: 'item' | 'divider';
  /** Text of menu item, can be choose one of text or i18n (Support HTML) */
  text?: string;
  /** I18n key of menu item, can be choose one of text or i18n (Support HTML) */
  i18n?: string;
  /** Whether to display the group name, default: true */
  group?: boolean;
  /** Routing for the menu item, can be choose one of link or externalLink */
  link?: string;
  /** External link for the menu item, can be choose one of link or externalLink */
  externalLink?: string;
  /** Specifies externalLink where to display the linked URL */
  target?: '_blank' | '_self' | '_parent' | '_top';
  /** Icon for the menu item, only valid for the first level menu */
  icon?: string | MenuIcon | null;
  /** Badge for the menu item when group is true */
  badge?: number;
  /** Whether to display a red dot instead of badge value */
  badgeDot?: boolean;
  /** Badge color */
  badgeStatus?: 'success' | 'processing' | 'default' | 'error' | 'warning';
  /** Maximum count to show in badge, show ${badgeOverflowCount}+ when exceed */
  badgeOverflowCount?: number;
  /** Whether disable for the menu item */
  disabled?: boolean;
  /** Whether hidden for the menu item */
  hide?: boolean;
  /** Whether hide in breadcrumbs, which are valid when the page-header component automatically generates breadcrumbs */
  hideInBreadcrumb?: boolean;
  /** ACL configuration, it's equivalent to ACLService.can(roleOrAbility: ACLCanType) parameter value */
  acl?: any;
  /** Whether shortcut menu item */
  shortcut?: boolean;
  /** Whether shortcut menu root node */
  shortcutRoot?: boolean;
  /** Whether to allow reuse, need to cooperate with the reuse-tab component */
  reuse?: boolean;
  /** Whether to expand, when checkStrictly is valid in sidebar-nav component */
  open?: boolean;
  /** Unique identifier of the menu item, can be used in getItem, setItem to update a menu */
  key?: string;
  /** Children menu of menu item */
  children?: Menu[];
  /** Additional properties */
  [key: string]: any;
}

interface MenuIcon {
  /** Type for icon - img, svg Size uses 14px width and height */
  type: 'class' | 'icon' | 'iconfont' | 'img' | 'svg';
  /** Value for the icon, can be set Class Name, nz-icon of nzType, image */
  value?: string | SafeHtml;
  /** Type of the ant design icon, default: outline */
  theme?: 'outline' | 'twotone' | 'fill';
  /** Rotate icon with animation, default: false */
  spin?: boolean;
  /** Only support the two-tone icon. Specific the primary color */
  twoToneColor?: string;
  /** Type of the icon from iconfont */
  iconfont?: string;
  /** Rotate degrees */
  rotate?: number;
}

Usage Examples:

import { Menu } from "@delon/theme";

// Complex menu with all features
const complexMenu: Menu[] = [
  {
    text: "Analytics",
    i18n: "menu.analytics",
    icon: "bar-chart",
    badge: 5,
    children: [
      {
        text: "Reports",
        link: "/analytics/reports",
        icon: { type: "file-text", theme: "outline" }
      },
      {
        text: "Live Data",
        link: "/analytics/live",
        badge: 12,
        badgeStatus: "processing"
      }
    ]
  },
  {
    text: "External Tool",
    externalLink: "https://example.com",
    target: "_blank",
    icon: "link"
  },
  {
    text: "Disabled Item",
    link: "/disabled",
    disabled: true,
    icon: "stop"
  }
];

// Menu with groups
const groupedMenu: Menu[] = [
  {
    text: "Core Features",
    group: true,
    children: [
      { text: "Dashboard", link: "/dashboard" },
      { text: "Profile", link: "/profile" }
    ]
  },
  {
    text: "Administration",
    group: true,
    children: [
      { text: "Users", link: "/admin/users" },
      { text: "Settings", link: "/admin/settings" }
    ]
  }
];

Menu Utilities

Helper functions and utilities for working with menu data.

/**
 * Visit all menu items recursively
 * Useful for menu tree traversal and manipulation
 */
function visitMenus<T>(
  data: T[],
  callback: (item: T, parent?: T, depth?: number) => void
): void;

/**
 * Find menu item in menu tree
 * Returns first matching menu item or null
 */
function findMenu(
  menus: Menu[],
  predicate: (menu: Menu) => boolean
): Menu | null;

/**
 * Get menu path from root to specified menu
 * Returns array of parent menus leading to target
 */
function getMenuPath(
  menus: Menu[],
  targetMenu: Menu
): Menu[];

Usage Examples:

import { MenuService } from "@delon/theme";

@Component({})
export class MenuUtilsExample {
  constructor(private menuService: MenuService) {}

  processAllMenus() {
    // Visit all menu items
    this.menuService.visit(this.menuService.menus, (menu, parent, depth) => {
      console.log(`${" ".repeat(depth || 0)}${menu.text}`);
      
      if (parent) {
        console.log(`  Parent: ${parent.text}`);
      }
    });
  }
  
  findUserMenu() {
    // Find specific menu
    const userMenu = this.menuService.find({
      cb: (menu) => menu.text === "Users"
    });
    
    if (userMenu) {
      console.log("Found user menu:", userMenu);
    }
  }
  
  getBreadcrumb() {
    // Get menu path for breadcrumb
    const path = this.menuService.getPathByUrl("/users/profile", true);
    const breadcrumb = path.map(menu => menu.text).join(" > ");
    console.log("Breadcrumb:", breadcrumb);
  }
}