Comprehensive menu management system for Angular applications providing hierarchical navigation, dynamic menu updates, URL-based routing integration, and flexible menu item configuration.
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);
}
}
}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" }
]
}
];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);
}
}