
Syncfusion Angular Menu
- 150 installs
- Updated August 4, 2026
- syncfusion/angular-ui-components-skills
Helps with ai & agent building tasks.
About
syncfusion-angular-menu is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- syncfusion-angular-menu
- AI & Agent Building
- AI-coding skill
Syncfusion Angular Menu by the numbers
- 150 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #3,337 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/syncfusion/angular-ui-components-skills --skill syncfusion-angular-menuAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 150 |
|---|---|
| Last updated | August 4, 2026 |
| Repository | syncfusion/angular-ui-components-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Implementing Syncfusion Angular Menu Component
The Menu component is a graphical user interface that serves as a navigation header for your application or site. It supports multi-level nested menu items, rich customization, data binding, templates, animations, comprehensive event handling, internationalization, state persistence, and mobile-friendly hamburger mode.
When to Use This Skill
Use this skill when you need to:
- Create navigation menus with hierarchical structures
- Bind data from arrays or remote sources
- Customize menu items dynamically (add, remove, enable, disable)
- Handle menu events (click, open, close, select)
- Apply animations and control orientation
- Add icons, images, and navigation URLs to menu items
- Implement accessibility features
- Style and theme the menu
- Build real-world patterns (sidebar menus, toolbar menus, context menus)
Component Overview
Key Features:
- ✅ Hierarchical and self-referential data binding
- ✅ HTML templates and custom item rendering
- ✅ Multi-level submenu support with nesting
- ✅ Multiple animation effects (FadeIn, SlideDown, ZoomIn)
- ✅ Horizontal and vertical orientation
- ✅ Icon and image support
- ✅ Enable/disable, show/hide menu items dynamically
- ✅ Event-driven customization
- ✅ RTL (Right-to-Left) support
- ✅ Accessibility features (keyboard navigation, ARIA)
- ✅ CSS customization and theming
Documentation and Navigation Guide
📖 Core Documentation (Start Here)
1️⃣ Getting Started
📄 Read: references/getting-started.md (Start here for new users)
- ✅ Installation and package setup
- ✅ Basic menu implementation
- ✅ CSS imports and theme configuration
- ✅ Essential properties (enablePersistence, enableRtl, cssClass, hoverDelay, enableScrolling, locale)
- ✅ Standalone architecture setup
- ✅ First application example
2️⃣ Data Source Binding and Templates
📄 Read: references/data-source-binding.md
- ✅ Hierarchical data binding with field mappings
- ✅ Self-referential data structures
- ✅ Remote data binding with DataManager
- ✅ HTML templates and custom item rendering
- ✅ Dynamic data population
- ✅ FieldSettingsModel configuration
3️⃣ Menu Items Customization & Dynamic Management
📄 Read: references/menu-items.md (Now includes methods!)
- ✅ Menu item properties (text, id, iconCss, url, separator)
- ✅ Icons and images in menu items
- ✅ Navigation URLs
- ✅ Custom attributes (htmlAttributes)
- ✅ NEW: Dynamic Methods Reference:
insertBefore()- Add items before a targetinsertAfter()- Add items after a targetremoveItems()- Delete menu itemsenableItems()- Enable/disable itemsshowItems()- Display hidden itemshideItems()- Hide menu items- ✅ Enable/disable menu items
- ✅ Show/hide menu items
- ✅ Add, remove, insert menu items dynamically
- ✅ Separators for grouping items
- ✅ Complete dynamic menu management example
4️⃣ Animation and Orientation
📄 Read: references/animation-and-orientation.md
- ✅ Animation settings (effect, duration, easing)
- ✅ Animation effects (FadeIn, SlideDown, ZoomIn, None)
- ✅ Horizontal and vertical orientation
- ✅ Sub-menu positioning and behavior
- ✅ Menu open-on-click vs hover behavior
5️⃣ Events and Interactions
📄 Read: references/events-and-interactions.md (Enhanced with event arguments)
- ✅ Event overview (7 events documented)
- ✅ NEW: Complete Event Arguments Documentation:
BeforeOpenCloseMenuEventArgs- beforeOpen/beforeClose eventsOpenCloseMenuEventArgs- onOpen/onClose eventsMenuEventArgs- select/beforeItemRender events- ✅ beforeOpen and beforeClose events (cancelable)
- ✅ onOpen and onClose events
- ✅ Select event handling
- ✅ beforeItemRender event (for tooltips and custom rendering)
- ✅ Created event for initialization
- ✅ Dynamic menu modification through events
🎯 Advanced Features & Patterns
6️⃣ Complete API Reference
📄 Read: references/api-reference.md (Comprehensive API documentation!)
- ✅ All MenuModel properties with examples (16 properties)
- ✅ All MenuItem properties with examples (7 properties)
- ✅ Animation settings configuration
- ✅ Field settings for data binding
- ✅ All Menu Methods with signatures and examples:
- insertBefore(), insertAfter(), removeItems()
- enableItems(), showItems(), hideItems()
- ✅ All Menu Events with arguments
- ✅ Event arguments interfaces
- ✅ Enumerations (Orientation, MenuEffect, MenuOpenType)
- ✅ Quick reference summary
7️⃣ Hamburger Mode and Responsive Design
📄 Read: references/hamburger-and-responsive.md (Mobile-first design)
- ✅ Hamburger mode overview
- ✅ hamburgerMode property configuration
- ✅ target and title properties
- ✅ Basic hamburger implementation
- ✅ Responsive toggle (Desktop/Mobile)
- ✅ Hamburger with animations
- ✅ Complete mobile menu solution
- ✅ State persistence with hamburger
- ✅ Accessibility best practices
- ✅ Touch-friendly interactions
8️⃣ Internationalization and State Management
📄 Read: references/internationalization-and-persistence.md (i18n + Storage)
- ✅ Localization (i18n) with 15+ locales
- ✅ RTL language support (Arabic, Hebrew, Persian)
- ✅ State persistence (enablePersistence)
- ✅ HTML sanitization (enableHtmlSanitizer)
- ✅ Hover delay control (hoverDelay)
- ✅ Custom CSS classes (cssClass)
- ✅ Advanced configuration patterns
- ✅ Complete setup example
9️⃣ Styling and Appearance
📄 Read: references/styling-and-appearance.md
- ✅ CSS styling and custom classes
- ✅ Rounded corner styling
- ✅ Title and icon positioning
- ✅ Theme customization
- ✅ Material3 and other theme integration
- ✅ CSS variable customization
🔟 Advanced Features and Scrolling
📄 Read: references/advanced-features.md
- ✅ Right-to-Left (RTL) language support
- ✅ Horizontal and vertical scrolling
- ✅ Large menu handling
- ✅ Menu item grouping with separators
- ✅ Accessibility features (keyboard navigation, ARIA)
- ✅ Nested submenu patterns
1️⃣1️⃣ Use Cases and Real-World Patterns
📄 Read: references/use-cases-and-scenarios.md
- ✅ Sidebar menu implementations
- ✅ Toolbar menu patterns
- ✅ Context menu scenarios
- ✅ ListView and other component integrations
- ✅ Multi-level menu structures
- ✅ Mobile navigation
- ✅ Admin dashboard patterns
- ✅ Best practices and common patterns
🔍 Quick Access by Task
I need to...
- ▶️ Install and get started → getting-started.md
- ▶️ Add dynamic menu items → menu-items.md
- ▶️ Bind data from array/API → data-source-binding.md
- ▶️ Handle menu clicks and events → events-and-interactions.md
- ▶️ Add animations → animation-and-orientation.md
- ▶️ Make mobile-friendly menu → hamburger-and-responsive.md
- ▶️ Support multiple languages → internationalization-and-persistence.md
- ▶️ Find all properties/methods/events → api-reference.md
- ▶️ Style and customize appearance → styling-and-appearance.md
- ▶️ Build production pattern → use-cases-and-scenarios.md
Quick Start Example
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `<ejs-menu [items]="menuItems"></ejs-menu>`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ separator: true },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{ text: 'Help' }
];
}CSS Setup:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';Common Patterns
1. Menu with Icons
public menuItems: MenuItemModel[] = [
{
text: 'File',
iconCss: 'e-icons e-file',
items: [
{ text: 'Open', iconCss: 'e-icons e-open' },
{ text: 'Save', iconCss: 'e-icons e-save' }
]
}
];2. Menu with Animation
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 800
};Use in template: <ejs-menu [animationSettings]="animationSettings"></ejs-menu>
3. Menu with Events
public onItemSelect(args: MenuEventArgs): void {
console.log('Selected item:', args.item.text);
}Use in template: <ejs-menu (select)="onItemSelect($event)"></ejs-menu>
4. Dynamic Menu Modification
@ViewChild('menu')
public menuObj?: MenuComponent;
public addItem(): void {
const newItem: MenuItemModel[] = [
{ text: 'New Menu Item' }
];
this.menuObj?.insertAfter(newItem, 'File', false);
}Complete API Reference
The Menu component includes 16 component properties, 7 menu item properties, 7 events, 3 animation properties, 6 field settings properties, and 10+ methods.
See [references/api-reference.md](references/api-reference.md) for complete documentation of:
- ✅ All MenuModel properties with examples
- ✅ All MenuItem properties with examples
- ✅ All methods (insertBefore, insertAfter, removeItems, enableItems, showItems, hideItems)
- ✅ All events with event arguments
- ✅ Enumerations and quick reference
Key Properties
Essential Properties (Most Used)
| Property | Type | Purpose | Default |
|---|---|---|---|
items | MenuItemModel[] | Array of menu items to render | [] |
fields | FieldSettingsModel | Maps data source fields to menu structure | - |
template | string | Custom template for menu items | - |
animationSettings | MenuAnimationSettingsModel | Controls menu animation behavior | - |
orientation | Orientation | 'Horizontal' or 'Vertical' | 'Horizontal' |
showItemOnClick | boolean | Open submenu on click (true) or hover (false) | false |
cssClass | string | Custom CSS class for styling | - |
enableRtl | boolean | Enable right-to-left layout | false |
Advanced Properties (For Special Cases)
| Property | Type | Purpose | Default |
|---|---|---|---|
hamburgerMode | boolean | Enable mobile hamburger menu | false |
target | string | Hamburger toggle button selector | - |
title | string | Title for hamburger drawer | - |
enablePersistence | boolean | Save menu state to localStorage | false |
enableScrolling | boolean | Enable scrollbar for large menus | false |
enableHtmlSanitizer | boolean | Sanitize HTML content (security) | true |
hoverDelay | number | Delay before submenu opens (ms) | 0 |
locale | string | Localization culture code | 'en-US' |
Common Use Cases
1. Application Menu Bar - File, Edit, View, Tools menus with submenus 2. Navigation Sidebar - Vertical menu for page navigation 3. Toolbar Menu - Horizontal menu with icons and commands 4. Context Menu - Right-click menu with dynamic items 5. Mobile Navigation - Collapsible menu for responsive design 6. Admin Dashboard - Multi-level navigation for admin panels
---
Feature Summary
✅ Data Binding
- Hierarchical menus (parent-child structure)
- Self-referential data (id/parentId relationships)
- Remote data binding via DataManager/HTTP
- Custom templates for items
✅ Customization
- Dynamic add/remove/insert menu items
- Enable/disable items (grayed out)
- Show/hide items programmatically
- Custom HTML attributes and content
- Icon and image support
- Navigation URLs
✅ Interactivity
- 7 events (beforeOpen, beforeClose, onOpen, onClose, select, beforeItemRender, created)
- Cancelable events for control flow
- Keyboard shortcuts support
- Mnemonic UI
✅ Styling & Theming
- 6+ built-in themes (Material3, Bootstrap5, Fluent2, etc.)
- Custom CSS classes
- Rounded corners support
- Icon/title customization
- Dark mode support
✅ Responsive & Mobile
- Hamburger mode for mobile
- RTL language support (15+ locales)
- Horizontal and vertical layouts
- Touch-friendly interactions
- Hamburger animations
✅ Performance & State
- State persistence to localStorage
- Hover delay control
- HTML sanitization for security
- Efficient rendering of large menus
Next Steps: 1. Start with getting-started.md for installation 2. Choose a reference based on your task (see "Quick Access by Task" above) 3. Refer to api-reference.md for complete API details
Advanced Features and Scrolling
Table of Contents
- Right-to-Left (RTL) Support
- Horizontal Scrolling
- Vertical Scrolling
- Large Menu Handling
- Menu Item Grouping
- Accessibility Features
- Nested Submenu Patterns
Right-to-Left (RTL) Support
Enable RTL layout for languages that read right-to-left (Arabic, Hebrew, Urdu, etc.) using the enableRtl property:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[enableRtl]="true"
[orientation]="'Horizontal'">
</ejs-menu>
</div>
`,
styles: [`
.e-section-control {
direction: rtl;
text-align: right;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'ملف', // File in Arabic
items: [
{ text: 'فتح' }, // Open
{ text: 'حفظ' }, // Save
{ text: 'خروج' } // Exit
]
},
{
text: 'تحرير', // Edit
items: [
{ text: 'قص' }, // Cut
{ text: 'نسخ' }, // Copy
{ text: 'لصق' } // Paste
]
},
{ text: 'مساعدة' } // Help
];
}RTL with HTML Direction
Set the HTML dir attribute for proper text alignment:
<!-- In index.html or component template -->
<html dir="rtl">
<head>...</head>
<body>
<app-root></app-root>
</body>
</html>Dynamic RTL Toggle
public enableRtl: boolean = false;
public toggleRtl(): void {
this.enableRtl = !this.enableRtl;
document.documentElement.dir = this.enableRtl ? 'rtl' : 'ltr';
}
// In template:
// <button (click)="toggleRtl()">Toggle RTL</button>Horizontal Scrolling
Enable horizontal scrolling for menus with many items that exceed container width:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[hScroll]="true">
</ejs-menu>
</div>
`,
styles: [`
.e-menu {
width: 500px; /* Constrain width to trigger scroll */
overflow-x: auto;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{ text: 'Home' },
{ text: 'About' },
{ text: 'Services' },
{ text: 'Products' },
{ text: 'Solutions' },
{ text: 'Resources' },
{ text: 'Contact' },
{ text: 'Blog' },
{ text: 'FAQ' },
{ text: 'Support' }
];
}hScroll Interface
export interface HScroll {
scrollStep?: number; // Pixels per scroll
scrollFrequency?: number; // Scroll speed (ms)
}
public hScroll: HScroll = {
scrollStep: 100,
scrollFrequency: 50
};Horizontal Scroll Example
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, hScrollModel } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[hScroll]="hScrollSettings">
</ejs-menu>
</div>
`
})
export class AppComponent {
public hScrollSettings: hScrollModel = {
enable: true,
scrollStep: 100
};
public menuItems: MenuItemModel[] = [
{ text: 'Dashboard' },
{ text: 'Accounts' },
{ text: 'Reports' },
{ text: 'Analytics' },
{ text: 'Performance' },
{ text: 'Revenue' },
{ text: 'Profit' },
{ text: 'Growth' }
];
}Vertical Scrolling
Enable vertical scrolling for dropdown menus with many items:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[vScroll]="vScrollSettings"
[orientation]="'Vertical'">
</ejs-menu>
</div>
`
})
export class AppComponent {
public vScrollSettings: any = {
enable: true,
scrollStep: 50
};
public menuItems: MenuItemModel[] = [
{ text: 'Item 1' },
{ text: 'Item 2' },
{ text: 'Item 3' },
{ text: 'Item 4' },
{ text: 'Item 5' },
{ text: 'Item 6' },
{ text: 'Item 7' },
{ text: 'Item 8' },
{ text: 'Item 9' },
{ text: 'Item 10' },
{ text: 'Item 11' },
{ text: 'Item 12' },
{ text: 'Item 13' },
{ text: 'Item 14' },
{ text: 'Item 15' }
];
}Large Menu Handling
Strategy 1: Split into Categories
Instead of a single large menu, organize items into logical categories:
public menuItems: MenuItemModel[] = [
{
text: 'File Management',
items: [
{ text: 'New', iconCss: 'e-icons e-new' },
{ text: 'Open', iconCss: 'e-icons e-open' },
{ text: 'Save', iconCss: 'e-icons e-save' }
]
},
{
text: 'Edit Tools',
items: [
{ text: 'Cut', iconCss: 'e-icons e-cut' },
{ text: 'Copy', iconCss: 'e-icons e-copy' },
{ text: 'Paste', iconCss: 'e-icons e-paste' }
]
},
{
text: 'View Options',
items: [
{ text: 'Zoom In' },
{ text: 'Zoom Out' },
{ text: 'Fit to Width' }
]
}
];Strategy 2: Implement Search
Add search functionality for large menus:
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<input
type="text"
placeholder="Search menu items..."
(keyup)="filterMenu($event)"
class="menu-search">
<ejs-menu [items]="filteredMenuItems"></ejs-menu>
</div>
`,
styles: [`
.menu-search {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
`]
})
export class AppComponent {
public allMenuItems: MenuItemModel[] = [
// ... large menu items
];
public filteredMenuItems: MenuItemModel[] = this.allMenuItems;
public filterMenu(event: Event): void {
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase();
if (!searchTerm) {
this.filteredMenuItems = this.allMenuItems;
return;
}
this.filteredMenuItems = this.allMenuItems.filter(item =>
item.text?.toLowerCase().includes(searchTerm)
);
}
}Menu Item Grouping
Use separators to organize related menu items:
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Recent', items: [
{ text: 'Document 1' },
{ text: 'Document 2' }
]},
{ separator: true },
{ text: 'Save' },
{ text: 'Save As' },
{ separator: true },
{ text: 'Exit' }
]
}
];Accessibility Features
Keyboard Navigation
The Menu supports full keyboard navigation:
| Key | Action |
|---|---|
Arrow Up/Down | Navigate menu items |
Arrow Left/Right | Open/close submenus |
Enter | Select item |
Escape | Close submenu |
ARIA Attributes
Ensure proper ARIA labels for screen readers:
public menuItems: MenuItemModel[] = [
{
text: 'File',
htmlAttributes: {
'role': 'menuitem',
'aria-label': 'File menu',
'aria-haspopup': 'true'
},
items: [
{
text: 'Open',
htmlAttributes: {
'role': 'menuitem',
'aria-label': 'Open file'
}
}
]
}
];Screen Reader Support
/* Hide visual-only elements from screen readers */
.icon-only::after {
content: attr(aria-label);
clip: rect(0 0 0 0);
overflow: hidden;
position: absolute;
}Complete Accessibility Example
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<nav role="navigation" aria-label="Main navigation">
<ejs-menu
[items]="menuItems"
role="menubar">
</ejs-menu>
</nav>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
htmlAttributes: {
'aria-label': 'File operations',
'role': 'menuitem'
},
items: [
{
text: 'New',
htmlAttributes: { 'aria-label': 'Create new document' }
},
{
text: 'Open',
htmlAttributes: { 'aria-label': 'Open existing document' }
}
]
}
];
}Nested Submenu Patterns
3-Level Deep Menu
public menuItems: MenuItemModel[] = [
{
text: 'Products',
items: [
{
text: 'Electronics',
items: [
{ text: 'Laptops' },
{ text: 'Phones' },
{ text: 'Tablets' }
]
},
{
text: 'Clothing',
items: [
{ text: 'Men' },
{ text: 'Women' },
{ text: 'Kids' }
]
}
]
}
];4-Level Deep Menu
public menuItems: MenuItemModel[] = [
{
text: 'Organization',
items: [
{
text: 'Departments',
items: [
{
text: 'Engineering',
items: [
{ text: 'Frontend' },
{ text: 'Backend' },
{ text: 'DevOps' }
]
},
{
text: 'Sales',
items: [
{ text: 'US Region' },
{ text: 'EU Region' },
{ text: 'Asia Region' }
]
}
]
}
]
}
];Limiting Submenu Depth
In practice, keep nesting to 2-3 levels for usability:
// ✅ Good: 2-3 levels
{
text: 'Level 1',
items: [
{
text: 'Level 2',
items: [
{ text: 'Level 3' }
]
}
]
}
// ⚠️ Avoid: 4+ levels
// Creates complex UX and harder to navigateComplete Advanced Features Example
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[enableRtl]="false"
[hScroll]="false"
orientation="Horizontal">
</ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'Catalog',
iconCss: 'e-icons e-list',
items: [
{ text: 'Electronics' },
{ text: 'Books' },
{ text: 'Clothing' }
]
},
{
text: 'Account',
iconCss: 'e-icons e-user',
items: [
{ text: 'Profile' },
{ text: 'Settings' },
{ separator: true },
{ text: 'Logout' }
]
}
];
}---
Next: For real-world implementation patterns, see references/use-cases-and-scenarios.md.
Animation and Orientation
Table of Contents
Animation Settings
Customize menu animation behavior using the animationSettings property of type MenuAnimationSettingsModel:
export interface MenuAnimationSettingsModel {
effect?: MenuEffect; // Animation effect type
duration?: number; // Duration in milliseconds
easing?: string; // CSS easing function
}Basic Animation Example
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuAnimationSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems" [animationSettings]="animationSettings"></ejs-menu>
</div>
`
})
export class AppComponent {
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 800,
easing: 'ease-in-out'
};
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{ text: 'Help' }
];
}Animation Effects
Available Effects
| Effect | Description |
|---|---|
None | No animation (instant display) |
SlideDown | Menu slides down from top |
ZoomIn | Menu zooms in from center |
FadeIn | Menu fades in gradually |
Effect Examples
SlideDown Effect
public animationSettings: MenuAnimationSettingsModel = {
effect: 'SlideDown',
duration: 400
};ZoomIn Effect
public animationSettings: MenuAnimationSettingsModel = {
effect: 'ZoomIn',
duration: 600,
easing: 'ease-out'
};FadeIn Effect
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 800
};No Animation
public animationSettings: MenuAnimationSettingsModel = {
effect: 'None'
};Animation Duration
Duration is specified in milliseconds:
{
effect: 'FadeIn',
duration: 300 // 300ms animation
}
{
effect: 'SlideDown',
duration: 600 // 600ms animation
}
{
effect: 'ZoomIn',
duration: 1000 // 1 second animation
}Easing Functions
CSS easing functions control animation timing:
{
effect: 'FadeIn',
duration: 800,
easing: 'ease' // Default
}
{
effect: 'FadeIn',
duration: 800,
easing: 'ease-in' // Slow start
}
{
effect: 'FadeIn',
duration: 800,
easing: 'ease-out' // Slow end
}
{
effect: 'FadeIn',
duration: 800,
easing: 'ease-in-out' // Slow start and end
}
{
effect: 'FadeIn',
duration: 800,
easing: 'linear' // Constant speed
}
{
effect: 'FadeIn',
duration: 800,
easing: 'cubic-bezier(0.25, 0.1, 0.25, 1)' // Custom
}Orientation
Control whether the menu displays horizontally or vertically using the orientation property:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<h3>Horizontal Menu (Default)</h3>
<ejs-menu [items]="menuItems" orientation="Horizontal"></ejs-menu>
<h3>Vertical Menu</h3>
<ejs-menu [items]="menuItems" orientation="Vertical"></ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{ text: 'Help' }
];
}Orientation Types
| Value | Description |
|---|---|
Horizontal | Menu items display in a horizontal row (default) |
Vertical | Menu items stack vertically (sidebar style) |
Horizontal Menu
orientation="Horizontal"
// or
[orientation]="'Horizontal'"Typical use cases:
- Application menu bars
- Top navigation bars
- Toolbar menus
Vertical Menu
orientation="Vertical"
// or
[orientation]="'Vertical'"Typical use cases:
- Sidebar navigation
- Left-side menus
- Mobile navigation
Menu Opening Behavior
Control how submenus open using the showItemOnClick property:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<h3>Click to Open Submenu</h3>
<ejs-menu [items]="menuItems" [showItemOnClick]="true"></ejs-menu>
<h3>Hover to Open Submenu (Default)</h3>
<ejs-menu [items]="menuItems" [showItemOnClick]="false"></ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{
text: 'View',
items: [
{
text: 'Toolbars',
items: [
{ text: 'Menu Bar' },
{ text: 'Bookmarks Toolbar' }
]
},
{
text: 'Zoom',
items: [
{ text: 'Zoom In' },
{ text: 'Zoom Out' }
]
}
]
},
{ text: 'Help' }
];
}Opening Behaviors
| Property | Value | Behavior |
|---|---|---|
showItemOnClick | false | Submenu opens on hover (default) |
showItemOnClick | true | Submenu opens on click only |
Click-Based Opening
public showItemOnClick: boolean = true;
// Template:
<ejs-menu [items]="menuItems" [showItemOnClick]="showItemOnClick"></ejs-menu>Use case: Mobile menus or touch devices where hover is not available.
Hover-Based Opening
public showItemOnClick: boolean = false; // or omit (default)
// Template:
<ejs-menu [items]="menuItems" [showItemOnClick]="false"></ejs-menu>Use case: Desktop applications with mouse interaction.
Sub-menu Position
Control the position of submenus relative to parent items. By default, submenus appear below or to the right of the parent. Use the beforeOpen event to customize positioning:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems" (beforeOpen)="beforeOpen($event)"></ejs-menu>
</div>
`,
styles: [`
.submenu-left {
/* Position submenu on left */
}
.submenu-right {
/* Position submenu on right (default) */
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
// Custom positioning logic before submenu opens
console.log('Submenu opening:', args.element);
}
}Sub-menu Positioning Patterns
Right Alignment (Default)
File Edit View
└─ New
└─ Submenu ──┐
└─ ItemNested Multi-level Menus
File
└─ New
└─ Project
└─ Web Application
└─ Template SelectionCustom Positioning with CSS
{
text: 'View',
items: [
{
text: 'Sidebar',
htmlAttributes: {
class: 'custom-position-left'
}
}
]
}Complete Animation and Orientation Example
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu
[items]="menuItems"
[animationSettings]="animationSettings"
[orientation]="'Horizontal'"
[showItemOnClick]="false">
</ejs-menu>
</div>
`
})
export class AppComponent {
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 600,
easing: 'ease-out'
};
public menuItems: MenuItemModel[] = [
// Your menu items
];
}---
Next: To handle menu events and customize behavior dynamically, see references/events-and-interactions.md.
Complete API Reference - Syncfusion Angular Menu Component
Table of Contents
- Component Properties
- MenuItem Properties
- Animation Settings
- Field Settings
- Menu Methods
- Menu Events
- Event Arguments
- Enumerations
---
Component Properties
Overview Table
| Property | Type | Default | Purpose |
|---|---|---|---|
items | MenuItemModel[] | [] | Array of menu items to render |
fields | FieldSettingsModel | - | Maps data source fields to menu structure |
template | `string \ | Function` | - |
animationSettings | MenuAnimationSettingsModel | - | Controls submenu animation behavior |
orientation | Orientation | 'Horizontal' | 'Horizontal' or 'Vertical' layout |
showItemOnClick | boolean | false | Open submenu on click (true) or hover (false) |
cssClass | string | - | Custom CSS class for styling |
enableRtl | boolean | false | Enable right-to-left layout |
enableScrolling | boolean | false | Enable scrollbar for large menus |
enablePersistence | boolean | false | Persist component state between page reloads |
enableHtmlSanitizer | boolean | true | Sanitize untrusted HTML content |
hamburgerMode | boolean | false | Enable hamburger menu mode |
target | string | - | Target element for hamburger mode toggle |
title | string | - | Title text displayed in hamburger mode |
hoverDelay | number | 0 | Delay (ms) before submenu opens on hover |
locale | string | 'en-US' | Localization culture code |
Detailed Property Documentation
items
Type: MenuItemModel[] | { [key: string]: Object }[] Purpose: Specifies menu items with their properties to render as the Menu component.
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];fields
Type: FieldSettingsModel Purpose: Maps data source fields to menu structure. Used for hierarchical and self-referential data binding.
public fields: FieldSettingsModel = {
text: 'name',
children: 'submenu',
iconCss: 'icon'
};
public menuData = [
{ id: 1, name: 'File', icon: 'e-file' },
{ id: 2, name: 'Edit', icon: 'e-edit' }
];template
Type: string | Function Purpose: Defines custom HTML template for rendering menu items.
// String template
public template: string = '<div class="custom-item">${text} - ${category}</div>';
// Function template
public template: Function = (data: any) => {
return `<span class="item-icon">${data.icon}</span><span>${data.text}</span>`;
};animationSettings
Type: MenuAnimationSettingsModel Purpose: Controls how submenu items animate when opened/closed.
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 400,
easing: 'ease-out'
};orientation
Type: Orientation - 'Horizontal' | 'Vertical' Purpose: Sets the layout direction of the menu.
// Horizontal menu bar (default)
<ejs-menu [orientation]="'Horizontal'"></ejs-menu>
// Vertical sidebar menu
<ejs-menu [orientation]="'Vertical'"></ejs-menu>showItemOnClick
Type: boolean Default: false Purpose: Controls submenu opening behavior.
// Click to open (mobile-friendly)
<ejs-menu [showItemOnClick]="true"></ejs-menu>
// Hover to open (desktop-friendly)
<ejs-menu [showItemOnClick]="false"></ejs-menu>cssClass
Type: string Purpose: Adds custom CSS classes to the menu wrapper for styling.
<ejs-menu [cssClass]="'dark-theme gradient-bg'"></ejs-menu>
/* CSS */
.dark-theme {
background: #333;
color: #fff;
}enableRtl
Type: boolean Default: false Purpose: Enables right-to-left layout for RTL languages (Arabic, Hebrew, etc.).
<ejs-menu [enableRtl]="true"></ejs-menu>
<!-- or dynamically -->
public toggleRtl(): void {
this.menuObj.enableRtl = !this.menuObj.enableRtl;
}enableScrolling
Type: boolean Default: false Purpose: Enables scrollbar when menu exceeds available space.
<ejs-menu [enableScrolling]="true"></ejs-menu>enablePersistence
Type: boolean Default: false Purpose: Persists menu state (open/closed submenus) to localStorage between page reloads.
<ejs-menu [enablePersistence]="true"></ejs-menu>
<!-- Menu state now saved and restored automatically -->enableHtmlSanitizer
Type: boolean Default: true Purpose: Sanitizes HTML content in menu items to prevent XSS attacks.
// Secure - removes potentially harmful scripts
<ejs-menu [enableHtmlSanitizer]="true"></ejs-menu>
// Unsafe - allows raw HTML (use with caution)
<ejs-menu [enableHtmlSanitizer]="false"></ejs-menu>hamburgerMode
Type: boolean Default: false Purpose: Converts menu into collapsible hamburger menu for mobile/responsive layouts.
@Component({
template: `
<ejs-menu
[hamburgerMode]="true"
[target]="'#menu-toggle'"
title="Menu">
</ejs-menu>
`
})
export class MenuComponent { }target
Type: string Purpose: Specifies DOM element (selector) to toggle hamburger menu. Usually a hamburger button.
<button id="menu-toggle" (click)="toggleMenu()">☰ Menu</button>
<ejs-menu
[hamburgerMode]="true"
[target]="'#menu-toggle'">
</ejs-menu>title
Type: string Purpose: Displays title text when hamburger mode is enabled.
<ejs-menu
[hamburgerMode]="true"
[title]="'Navigation Menu'">
</ejs-menu>hoverDelay
Type: number Default: 0 Purpose: Sets millisecond delay before submenu opens on hover. Useful for preventing accidental menu opens.
// 300ms delay before submenu opens
<ejs-menu [hoverDelay]="300"></ejs-menu>locale
Type: string Default: 'en-US' Purpose: Sets UI culture/language for localized content.
// Arabic locale
<ejs-menu [locale]="'ar-AE'"></ejs-menu>
// Spanish locale
<ejs-menu [locale]="'es-ES'"></ejs-menu>---
MenuItem Properties
Overview Table
| Property | Type | Purpose |
|---|---|---|
text | string | Display text for the menu item |
id | string | Unique identifier for the menu item |
iconCss | string | CSS class for icon styling |
url | string | Navigation URL (creates anchor link) |
items | MenuItemModel[] | Submenu items array |
separator | boolean | Creates visual separator line |
htmlAttributes | Record<string, any> | Custom HTML attributes |
Detailed Documentation
text
Type: string Purpose: Display text shown for the menu item.
{ text: 'File' }
{ text: 'New Document' }id
Type: string Purpose: Unique identifier for referencing the menu item in methods and events.
{ id: 'file-menu', text: 'File' }
{ id: 'file-new', text: 'New', parentId: 'file-menu' }
// Reference in code
this.menuObj.enableItems(['file-new'], true);iconCss
Type: string Purpose: CSS classes for icon display. Supports font icons and sprite images.
// Font icon
{ text: 'File', iconCss: 'e-icons e-file' }
// Multiple classes
{ text: 'Save', iconCss: 'fas fa-save custom-icon' }
// Sprite image
{ text: 'Undo', iconCss: 'sprite-icon undo' }url
Type: string Purpose: Navigation URL. Creates anchor link from the menu item.
{ text: 'Google', url: 'https://www.google.com' }
{ text: 'Dashboard', url: '/dashboard' }
{ text: 'Users', url: '/admin/users#list' }items
Type: MenuItemModel[] Purpose: Array of submenu items, creating hierarchical menu structure.
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Save As' }
]
}separator
Type: boolean Default: false Purpose: Creates horizontal (horizontal menu) or vertical (vertical menu) separator line to group items.
[
{ text: 'New' },
{ text: 'Open' },
{ separator: true }, // Visual divider
{ text: 'Exit' }
]htmlAttributes
Type: Record<string, any> Purpose: Adds custom HTML attributes to the rendered menu item element.
{
text: 'Settings',
htmlAttributes: {
'class': 'premium-item',
'data-permission': 'admin',
'title': 'Advanced Settings',
'aria-label': 'Application Settings'
}
}---
Animation Settings
MenuAnimationSettingsModel
Properties:
| Property | Type | Purpose |
|---|---|---|
effect | MenuEffect | Animation effect to apply |
duration | number | Duration in milliseconds |
easing | string | CSS easing function |
Effect Options
- None - No animation, instant appearance
- SlideDown - Submenu slides down
- ZoomIn - Submenu zooms in from center
- FadeIn - Submenu fades in
Easing Options
ease,ease-in,ease-out,ease-in-out,linearcubic-bezier(n,n,n,n)for custom easing
Examples
// Fast fade-in
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 200,
easing: 'linear'
};
// Smooth slide down
public animationSettings: MenuAnimationSettingsModel = {
effect: 'SlideDown',
duration: 500,
easing: 'ease-out'
};
// Bounce zoom
public animationSettings: MenuAnimationSettingsModel = {
effect: 'ZoomIn',
duration: 600,
easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)'
};
// No animation
public animationSettings: MenuAnimationSettingsModel = {
effect: 'None'
};
// Usage in template
<ejs-menu [animationSettings]="animationSettings"></ejs-menu>---
Field Settings
FieldSettingsModel
Maps data source fields to menu structure properties:
| Property | Type | Purpose |
|---|---|---|
text | `string \ | string[]` |
children | `string \ | string[]` |
itemId | `string \ | string[]` |
parentId | `string \ | string[]` |
iconCss | `string \ | string[]` |
url | `string \ | string[]` |
separator | `string \ | string[]` |
Hierarchical Data Example
public fields: FieldSettingsModel = {
text: 'name',
children: 'submenu',
iconCss: 'icon'
};
public hierarchicalData = [
{
name: 'File',
icon: 'e-icons e-file',
submenu: [
{ name: 'Open', icon: 'e-icons e-open' },
{ name: 'Save', icon: 'e-icons e-save' }
]
}
];Self-Referential Data Example
public fields: FieldSettingsModel = {
text: 'name',
itemId: 'id',
parentId: 'parentId'
};
public selfRefData = [
{ id: 1, name: 'File', parentId: null },
{ id: 2, name: 'Open', parentId: 1 },
{ id: 3, name: 'Save', parentId: 1 },
{ id: 4, name: 'Edit', parentId: null }
];---
Menu Methods
insertBefore()
Signature: insertBefore(items: MenuItemModel[], target: string, isUniqueId?: boolean): void
Purpose: Insert menu items before a target item.
@ViewChild('menu')
public menuObj?: MenuComponent;
public insertNewItem(): void {
const newItems: MenuItemModel[] = [
{ text: 'New Item', id: 'new-1' }
];
// Insert before 'Save' item
this.menuObj?.insertBefore(newItems, 'Save', false);
}Parameters:
items- Array of MenuItemModel items to inserttarget- Text or ID of target item (reference point)isUniqueId- If true, target is treated as unique ID; if false, treated as text
insertAfter()
Signature: insertAfter(items: MenuItemModel[], target: string, isUniqueId?: boolean): void
Purpose: Insert menu items after a target item.
public insertAfterItem(): void {
const newItems: MenuItemModel[] = [
{ text: 'Insert After', id: 'insert-1' }
];
// Insert after 'File' menu
this.menuObj?.insertAfter(newItems, 'File', false);
}removeItems()
Signature: removeItems(items: string[], isUniqueId?: boolean): void
Purpose: Remove menu items by text or ID.
public removeItem(): void {
// Remove by text
this.menuObj?.removeItems(['Save'], false);
// Remove by ID
this.menuObj?.removeItems(['save-id'], true);
}Parameters:
items- Array of item texts or IDs to removeisUniqueId- If true, items treated as IDs; if false, treated as text
enableItems()
Signature: enableItems(items: string[], enable: boolean, isUniqueId?: boolean): void
Purpose: Enable or disable menu items.
public disableItems(): void {
// Disable by text
this.menuObj?.enableItems(['Export', 'Print'], false, false);
}
public enableItems(): void {
// Enable by ID
this.menuObj?.enableItems(['export-id', 'print-id'], true, true);
}Parameters:
items- Array of item texts or IDsenable- true to enable, false to disableisUniqueId- If true, items treated as IDs
showItems()
Signature: showItems(items: string[], isUniqueId?: boolean): void
Purpose: Show hidden header-level menu items. (Note: Only affects header items initially hidden)
public showItem(): void {
// Show by text
this.menuObj?.showItems(['Tools'], false);
// Show by ID
this.menuObj?.showItems(['tools-id'], true);
}hideItems()
Signature: hideItems(items: string[], isUniqueId?: boolean): void
Purpose: Hide header-level menu items.
public hideItem(): void {
// Hide by text
this.menuObj?.hideItems(['Admin'], false);
}setItem()
Signature: setItem(item: MenuItemModel, id?: string, isUniqueId?: boolean): void
Purpose: Update or set a menu item properties in the Menu component.
public updateMenuItem(): void {
// Update by text
this.menuObj?.setItem(
{ text: 'Updated File', iconCss: 'e-icons e-file' },
'File',
false
);
}
public updateByItemId(): void {
// Update by unique ID
this.menuObj?.setItem(
{ text: 'Save As...', id: 'save-as', iconCss: 'e-icons e-save' },
'save-id',
true
);
}Parameters:
item- MenuItemModel with updated propertiesid- Optional text or ID of the item to updateisUniqueId- If true, id is treated as unique ID; if false, treated as text
open()
Signature: open(): void
Purpose: Opens the Menu in hamburger mode. This method expands the hamburger menu when it's in collapsed state.
@ViewChild('menu')
public menuObj?: MenuComponent;
public openHamburgerMenu(): void {
// Open hamburger menu programmatically
this.menuObj?.open();
}
// Usage example with button
public onHamburgerButtonClick(): void {
this.menuObj?.open();
// Apply animations or additional logic
}Returns: void
getItemIndex()
Signature: getItemIndex(item: MenuItemModel | string, isUniqueId?: boolean): number[]
Purpose: Get the index path of a menu item in the Menu hierarchy. Returns array of indices representing the item's position.
public findItemIndex(): void {
// Get index by text
const index: number[] | undefined = this.menuObj?.getItemIndex('Save', false);
console.log('Item index path:', index); // e.g., [0, 1] for File > Save
}
public findItemById(): void {
// Get index by unique ID
const index: number[] | undefined = this.menuObj?.getItemIndex('save-id', true);
console.log('Item index path:', index);
}
public navigateToItem(): void {
const itemIndex = this.menuObj?.getItemIndex('Export', false);
if (itemIndex) {
console.log(`Item is at level ${itemIndex.length}, position ${itemIndex[itemIndex.length - 1]}`);
}
}Parameters:
item- MenuItemModel or string (text/ID) to searchisUniqueId- If true, item is treated as unique ID; if false, treated as text
Returns: number[] - Array of indices representing hierarchical position (e.g., [0, 2] = first top-level item, third child)
close()
Signature: close(): void
Purpose: Closes the Menu if it is opened in hamburger mode. This method collapses the hamburger menu when it's in expanded state.
@ViewChild('menu')
public menuObj?: MenuComponent;
public closeHamburgerMenu(): void {
// Close hamburger menu programmatically
this.menuObj?.close();
}
// Usage example: close on item selection
public onMenuItemSelected(): void {
this.menuObj?.close(); // Auto-collapse after selection
}
// Auto-close on outside click
public onBackdropClick(): void {
this.menuObj?.close();
}Returns: void
destroy()
Signature: destroy(): void
Purpose: Destroys the Menu component and releases all resources. Removes event listeners and DOM elements created by the component.
@ViewChild('menu')
public menuObj?: MenuComponent;
public ngOnDestroy(): void {
// Clean up menu component on component destruction
this.menuObj?.destroy();
}
// Manual cleanup if needed
public cleanupMenu(): void {
this.menuObj?.destroy();
console.log('Menu component destroyed');
}Returns: void
Note: This method is typically called automatically during Angular's ngOnDestroy lifecycle hook. Use manually only when explicitly removing the component.
---
Menu Events
Overview Table
| Event | Arguments | Cancelable | Purpose |
|---|---|---|---|
beforeOpen | BeforeOpenCloseMenuEventArgs | Yes | Before submenu opens |
beforeClose | BeforeOpenCloseMenuEventArgs | Yes | Before submenu closes |
onOpen | OpenCloseMenuEventArgs | No | After submenu opened |
onClose | OpenCloseMenuEventArgs | No | After submenu closed |
select | MenuEventArgs | No | Menu item selected/clicked |
beforeItemRender | MenuEventArgs | No | Before item renders |
created | Event | No | Component initialization complete |
Event Examples
export class MenuComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public onBeforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
console.log('Submenu opening:', args);
// Prevent opening specific items
if (args.item?.text === 'Admin') {
args.cancel = true;
}
}
public onBeforeClose(args: BeforeOpenCloseMenuEventArgs): void {
console.log('Submenu closing:', args);
// Prevent closure
args.cancel = true;
}
public onOpen(args: OpenCloseMenuEventArgs): void {
console.log('Submenu opened:', args);
}
public onClose(args: OpenCloseMenuEventArgs): void {
console.log('Submenu closed:', args);
}
public onSelect(args: MenuEventArgs): void {
console.log('Item selected:', args.item?.text);
// Navigate or trigger action
}
public onBeforeItemRender(args: MenuEventArgs): void {
console.log('Item rendering:', args.item?.text);
// Customize item appearance
}
public onCreated(): void {
console.log('Menu created successfully');
// Initialize after render
}
}---
Event Arguments
BeforeOpenCloseMenuEventArgs
Properties:
interface BeforeOpenCloseMenuEventArgs {
name: string; // Event name: 'beforeOpen' or 'beforeClose'
item?: MenuItemModel; // Menu item being opened/closed
element?: HTMLElement; // DOM element of the menu
cancel?: boolean; // Set to true to prevent action
}Example:
public beforeOpenHandler(args: BeforeOpenCloseMenuEventArgs): void {
if (args.item?.id === 'admin-menu') {
// Prevent admin menu from opening for non-admins
args.cancel = !this.isAdmin;
}
}OpenCloseMenuEventArgs
Properties:
interface OpenCloseMenuEventArgs {
name: string; // Event name: 'onOpen' or 'onClose'
item?: MenuItemModel; // Menu item opened/closed
element?: HTMLElement; // DOM element of the menu
}Example:
public onOpenHandler(args: OpenCloseMenuEventArgs): void {
console.log('Menu item opened:', args.item?.text);
// Highlight or animate
args.element?.classList.add('active');
}MenuEventArgs
Properties:
interface MenuEventArgs {
name: string; // Event name: 'select' or 'beforeItemRender'
item?: MenuItemModel; // Menu item being rendered/selected
element?: HTMLElement; // DOM element of the menu item
}Example:
public beforeItemRenderHandler(args: MenuEventArgs): void {
if (args.item?.id === 'premium-feature') {
args.element?.classList.add('premium-badge');
args.element?.setAttribute('data-premium', 'true');
}
}
public selectHandler(args: MenuEventArgs): void {
console.log('Selected:', args.item?.text);
this.navigateTo(args.item?.url || '/');
}---
Enumerations
Orientation
enum Orientation {
Horizontal = 'Horizontal', // Menu bar layout (default)
Vertical = 'Vertical' // Sidebar layout
}MenuEffect
enum MenuEffect {
None = 'None', // Instant appearance
SlideDown = 'SlideDown', // Slide down animation
ZoomIn = 'ZoomIn', // Zoom in animation
FadeIn = 'FadeIn' // Fade in animation
}MenuOpenType
enum MenuOpenType {
Auto = 'Auto', // Auto detection (click or hover based on showItemOnClick)
Click = 'Click', // Open only on click
Hover = 'Hover' // Open only on hover
}---
Quick Reference Summary
Most Common Properties
@Component({
template: `
<ejs-menu
[items]="menuItems"
[animationSettings]="animationSettings"
[orientation]="'Horizontal'"
[showItemOnClick]="false"
(select)="onItemSelect($event)">
</ejs-menu>
`
})
export class MenuComponent {
public menuItems: MenuItemModel[] = [
{ text: 'File', items: [{ text: 'Open' }, { text: 'Save' }] },
{ text: 'Edit', items: [{ text: 'Cut' }, { text: 'Copy' }] }
];
public animationSettings: MenuAnimationSettingsModel = {
effect: 'FadeIn',
duration: 300
};
public onItemSelect(args: MenuEventArgs): void {
console.log('Selected:', args.item?.text);
}
}Most Common Methods
// Add items
this.menuObj?.insertAfter([{ text: 'New' }], 'File', false);
// Remove items
this.menuObj?.removeItems(['Old'], false);
// Enable/Disable
this.menuObj?.enableItems(['Export'], false, false);
// Hide/Show
this.menuObj?.hideItems(['Admin'], false);Most Common Events
(beforeOpen)="onBeforeOpen($event)"
(select)="onSelect($event)"
(beforeItemRender)="onBeforeItemRender($event)"
(created)="onCreated()"Data Source Binding and Templates
Table of Contents
- Overview
- Hierarchical Data Binding
- Self-Referential Data
- Remote Data with DataManager
- Custom Menu Templates
- Field Mapping
Overview
The Menu component supports flexible data binding from multiple sources:
- Hierarchical arrays (nested data structures)
- Self-referential data (with parentId/itemId)
- Remote data (DataManager with ODataV4Adaptor)
- Custom templates (HTML template engine)
Hierarchical Data Binding
Basic Hierarchical Structure
Bind the Menu to a hierarchical data source by assigning it to the items property and mapping fields with the fields property:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="data" [fields]="menuFields"></ejs-menu>
</div>
`
})
export class AppComponent {
public data: { [key: string]: Object }[] = [
{
continent: 'Asia',
countries: [
{ country: 'China' },
{ country: 'India' },
{ country: 'Japan' }
]
},
{
continent: 'North America',
countries: [
{ country: 'Canada' },
{ country: 'Mexico' },
{ country: 'USA' }
]
},
{
continent: 'Europe',
countries: [
{ country: 'France' },
{ country: 'Germany' },
{ country: 'UK' }
]
},
{ continent: 'Africa' }
];
public menuFields: FieldSettingsModel = {
text: ['continent', 'country'],
children: ['countries']
};
}Field Mapping Options
The FieldSettingsModel supports:
| Property | Type | Description |
|---|---|---|
text | string[] | Array of field names to display as menu text |
children | string[] | Array of field names containing nested items |
itemId | string | Unique identifier for each item (optional) |
parentId | string | Parent identifier for self-referential data (optional) |
Self-Referential Data
Self-referential data uses itemId and parentId to establish hierarchical relationships. Items with null parentId are root-level items.
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="data" [fields]="menuFields"></ejs-menu>
</div>
`
})
export class AppComponent {
public data: { [key: string]: Object }[] = [
{ id: 'parent1', text: 'Events' },
{ id: 'parent2', text: 'Movies' },
{ id: 'parent3', text: 'Directory' },
{ id: 'parent4', text: 'Queries', pId: null as any },
{ id: 'parent5', text: 'Services', pId: null as any },
{ id: 'parent6', text: 'Conferences', pId: 'parent1' },
{ id: 'parent7', text: 'Music', pId: 'parent1' },
{ id: 'parent8', text: 'Workshops', pId: 'parent1' },
{ id: 'parent9', text: 'Now Showing', pId: 'parent2' },
{ id: 'parent10', text: 'Coming Soon', pId: 'parent2' },
{ id: 'parent11', text: 'Media Gallery', pId: 'parent3' },
{ id: 'parent12', text: 'Newsletters', pId: 'parent3' },
{ id: 'parent13', text: 'Our Policy', pId: 'parent4' },
{ id: 'parent14', text: 'Site Map', pId: 'parent4' },
{ id: 'parent15', text: 'Pop', pId: 'parent7' },
{ id: 'parent16', text: 'Folk', pId: 'parent7' },
{ id: 'parent17', text: 'Classical', pId: 'parent7' }
];
public menuFields: FieldSettingsModel = {
itemId: 'id',
text: 'text',
parentId: 'pId'
};
}Root-Level Items
Root-level items have no parent. Specify parentId as null or omit it entirely:
// Both are valid root-level items:
{ id: 'item1', text: 'File', pId: null }
{ id: 'item2', text: 'Edit' } // pId omittedRemote Data with DataManager
Use Syncfusion's DataManager to bind the Menu to remote data sources. This example uses the Northwind OData service:
import { Component, OnInit } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { DataManager, Query, ODataV4Adaptor, ReturnOption } from '@syncfusion/ej2-data';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu *ngIf="menuItems" [items]="menuItems" [fields]="menuFields"></ejs-menu>
</div>
`
})
export class AppComponent implements OnInit {
private SERVICE_URI: string = 'https://services.odata.org/V4/Northwind/Northwind.svc/';
public menuFields: FieldSettingsModel = {
text: ['FirstName', 'ShipName'],
children: ['Orders']
};
public menuItems?: { [key: string]: Object }[];
public ngOnInit(): void {
// Getting remote data using DataManager
new DataManager({
url: this.SERVICE_URI,
adaptor: new ODataV4Adaptor(),
crossDomain: true
})
.executeQuery(
new Query()
.from('Employees')
.take(5)
.hierarchy(
new Query()
.foreignKey('EmployeeID')
.from('Orders')
.take(13),
function () {
return [1, 2, 3, 4, 5];
}
)
)
.then((e: ReturnOption) => {
// Assign result data to menu items
this.menuItems = e.result as { [key: string]: Object }[];
});
}
}DataManager Configuration
new DataManager({
url: 'https://your-api-endpoint.com/data',
adaptor: new ODataV4Adaptor(), // For OData v4 services
crossDomain: true
})Supported adaptors:
ODataV4Adaptor- For OData v4 servicesUrlAdaptor- For custom REST APIsJsonServerAdaptor- For JSON ServerWebApiAdaptor- For ASP.NET Web API
Custom Menu Templates
Use Angular templates to customize menu item rendering. Templates allow displaying complex HTML structures beyond simple text:
import { Component, Inject } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { FieldSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { MenuAnimationSettingsModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
import { ButtonModule } from '@syncfusion/ej2-angular-buttons';
enableRipple(false);
@Component({
imports: [MenuModule, ButtonModule],
standalone: true,
selector: 'app-root',
styleUrls: ['./template.css'],
template: `
<div class="e-section-control">
<div id="menuTemplate" class="menu-section">
<div class="menu-control">
<ejs-menu
[items]="dataSource"
[fields]="menuFields"
[animationSettings]="animation"
cssClass="e-template-menu">
<ng-template #template let-dataSource="">
{{dataSource.category}}
<div *ngIf="dataSource.value" style="width:100%;display:flex;justify-content:space-between;">
<img *ngIf="dataSource.url" class="e-avatar e-avatar-small"
src="{{dataSource.url}}.png" />
<span style="width:100%;">{{dataSource.value}}</span>
<span *ngIf="dataSource.count" class="e-badge e-badge-success">
{{dataSource.count}}
</span>
</div>
</ng-template>
</ejs-menu>
</div>
</div>
</div>
`
})
export class AppComponent {
public dataSource: { [key: string]: Object }[] = [
{
category: 'Products',
options: [
{ value: 'JavaScript', url: 'javascript' },
{ value: 'Angular', url: 'angular' },
{ value: 'ASP.NET Core', url: 'core' },
{ value: 'ASP.NET MVC', url: 'mvc' }
]
},
{
category: 'Services',
options: [
{ value: 'Application Development', count: '1200+' },
{ value: 'Maintenance & Support', count: '3700+' },
{ value: 'Quality Assurance' },
{ value: 'Cloud Integration', count: '900+' }
]
},
{ category: 'Careers' },
{ category: 'Sign In' }
];
public menuFields: object = {
text: ['category', 'value'],
children: ['options']
};
public animation: any = { effect: 'FadeIn', duration: 400 };
}Template Features
- Data Binding: Use
{{property}}to display data - Conditionals: Use
*ngIffor conditional rendering - Images: Bind image URLs with
srcbinding - Badges: Display badges or counts
- Icons: Include Syncfusion icon classes
- Interactive Elements: Add buttons or links
Preventing Submenu Closing
In templates, to prevent submenus from closing when interacting with custom content, set args.cancel to true in the beforeClose event:
public beforeClose(args: BeforeOpenCloseMenuEventArgs): void {
// Prevent closing when custom content is clicked
args.cancel = true;
}Field Mapping
Multiple Text Fields
Display multiple fields as the menu text at different levels:
public menuFields: FieldSettingsModel = {
text: ['continent', 'country', 'city'],
children: ['countries', 'cities']
};This creates a three-level hierarchy where:
- Level 1:
continentfield - Level 2:
countryfield - Level 3:
cityfield
Dynamic Field Mapping
Adjust field mappings based on data structure complexity. For flat data with self-reference:
public menuFields: FieldSettingsModel = {
itemId: 'empID',
text: 'empName',
parentId: 'parentID'
};---
Next: To customize individual menu items, styling, and interactions, see references/menu-items.md.
Events and Interactions
Table of Contents
- Event Overview
- beforeOpen Event
- beforeClose Event
- onOpen Event
- onClose Event
- select Event
- created Event
- beforeItemRender Event
- Practical Examples
Event Overview
The Menu component provides lifecycle and interaction events to enable dynamic customization:
| Event | Arguments | Purpose | Cancelable |
|---|---|---|---|
beforeOpen | BeforeOpenCloseMenuEventArgs | Before submenu opens | Yes |
beforeClose | BeforeOpenCloseMenuEventArgs | Before submenu closes | Yes |
onOpen | OpenCloseMenuEventArgs | After submenu opens | No |
onClose | OpenCloseMenuEventArgs | After submenu closes | No |
select | MenuEventArgs | Menu item selected | No |
beforeItemRender | MenuEventArgs | Before menu item renders | No |
created | Event | Menu initialized | No |
Event Arguments Details
BeforeOpenCloseMenuEventArgs
Used by beforeOpen and beforeClose events.
interface BeforeOpenCloseMenuEventArgs {
// Event metadata
name: string; // Event name: 'beforeOpen' or 'beforeClose'
// Menu item information
item?: MenuItemModel; // The menu item being opened/closed
// DOM element reference
element?: HTMLElement; // The DOM element of the menu
// Cancelation control
cancel?: boolean; // Set to true to prevent opening/closing
}Usage Example:
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
console.log('Event:', args.name); // 'beforeOpen'
console.log('Item text:', args.item?.text); // Menu item text
console.log('Item ID:', args.item?.id); // Menu item ID
console.log('Element classes:', args.element?.className);
// Prevent opening if user is not authorized
if (args.item?.id === 'admin-menu' && !this.isAdmin) {
args.cancel = true; // Block opening
}
}OpenCloseMenuEventArgs
Used by onOpen and onClose events (cannot be canceled).
interface OpenCloseMenuEventArgs {
// Event metadata
name: string; // Event name: 'onOpen' or 'onClose'
// Menu item information
item?: MenuItemModel; // The menu item opened/closed
// DOM element reference
element?: HTMLElement; // The DOM element of the menu
}Usage Example:
public onOpen(args: OpenCloseMenuEventArgs): void {
console.log('Menu opened:', args.item?.text);
// Apply styling
args.element?.classList.add('opened');
args.element?.style.backgroundColor = '#f0f0f0';
}
public onClose(args: OpenCloseMenuEventArgs): void {
console.log('Menu closed:', args.item?.text);
// Remove styling
args.element?.classList.remove('opened');
args.element?.style.backgroundColor = '';
}MenuEventArgs
Used by select and beforeItemRender events.
interface MenuEventArgs {
// Event metadata
name: string; // Event name: 'select' or 'beforeItemRender'
// Menu item information
item?: MenuItemModel; // The menu item being rendered/selected
// DOM element reference
element?: HTMLElement; // The DOM element of the menu item
}Usage Example:
public select(args: MenuEventArgs): void {
console.log('Selected item:', args.item?.text);
console.log('Item URL:', args.item?.url);
console.log('Element:', args.element?.innerText);
}
public beforeItemRender(args: MenuEventArgs): void {
// Add custom attributes
if (args.item?.id === 'premium') {
args.element?.setAttribute('data-premium', 'true');
args.element?.classList.add('premium-item');
}
}beforeOpen Event
Triggered before a submenu opens. Allows modification of content or prevention of opening.
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, BeforeOpenCloseMenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<div class="status">{{ status }}</div>
<ejs-menu [items]="menuItems" (beforeOpen)="beforeOpen($event)"></ejs-menu>
</div>
`
})
export class AppComponent {
public status: string = '';
public menuItems: MenuItemModel[] = [
{
text: 'Events',
items: [
{ text: 'Conferences' },
{ text: 'Music' },
{ text: 'Workshops' }
]
},
{
text: 'Movies',
items: [
{ text: 'Now Showing' },
{ text: 'Coming Soon' }
]
}
];
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
this.status = 'Opening submenu: ' + args.items[0]?.text;
// Optional: Prevent opening under certain conditions
// args.cancel = true;
}
}BeforeOpenCloseMenuEventArgs Properties
export interface BeforeOpenCloseMenuEventArgs {
element?: HTMLElement; // DOM element
items?: MenuItemModel[]; // Menu items array
parentItem?: MenuItemModel; // Parent menu item
event?: Event; // Event object
cancel?: boolean; // Cancel the operation
}Use Cases
- Validate before opening
- Modify submenu items dynamically
- Prevent opening under conditions
- Load data on-demand
- Handle submenu visibility
Preventing Submenu Opening
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
// Prevent opening specific submenus
if (args.parentItem?.text === 'Admin') {
args.cancel = true;
}
}beforeClose Event
Triggered before a submenu closes. Allows preventing closure or cleanup operations.
public beforeClose(args: BeforeOpenCloseMenuEventArgs): void {
this.status = 'Closing submenu';
// Prevent closing custom elements
if (args.element?.querySelector('input:focus')) {
args.cancel = true; // Keep open if input is focused
}
}onOpen Event
Triggered after a submenu successfully opens. Useful for post-open adjustments.
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, OpenCloseMenuEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<div class="event-log">{{ eventLog }}</div>
<ejs-menu [items]="menuItems" (onOpen)="onOpen($event)"></ejs-menu>
</div>
`
})
export class AppComponent {
public eventLog: string = '';
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [{ text: 'Open' }, { text: 'Save' }]
}
];
public onOpen(args: OpenCloseMenuEventArgs): void {
this.eventLog = 'Submenu opened successfully';
// Apply custom styling or focus management
if (args.element) {
args.element.style.background = 'lightblue';
}
}
}OpenCloseMenuEventArgs Properties
export interface OpenCloseMenuEventArgs {
element?: HTMLElement; // DOM element
items?: MenuItemModel[]; // Menu items array
parentItem?: MenuItemModel; // Parent menu item
event?: Event; // Event object
}onClose Event
Triggered after a submenu closes. Useful for cleanup tasks.
public onClose(args: OpenCloseMenuEventArgs): void {
console.log('Submenu closed');
// Cleanup operations
this.eventLog = 'Submenu closed, cleaning up...';
// Remove styles applied on open
if (args.element) {
args.element.style.background = '';
}
}select Event
Triggered when a menu item is selected/clicked. Used for custom navigation or actions.
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<div class="selection-log">{{ selectedItem }}</div>
<ejs-menu [items]="menuItems" (select)="onItemSelect($event)"></ejs-menu>
</div>
`
})
export class AppComponent {
public selectedItem: string = '';
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
}
];
public onItemSelect(args: MenuEventArgs): void {
this.selectedItem = 'Selected: ' + args.item?.text;
// Custom navigation logic
if (args.item?.text === 'New') {
this.handleNewFile();
} else if (args.item?.text === 'Open') {
this.handleOpenFile();
}
}
private handleNewFile(): void {
console.log('Creating new file...');
}
private handleOpenFile(): void {
console.log('Opening file dialog...');
}
}MenuEventArgs Properties
export interface MenuEventArgs {
element?: HTMLElement; // DOM element
item?: MenuItemModel; // Selected item
parentItem?: MenuItemModel; // Parent item
event?: Event; // Event object
}created Event
Triggered when the Menu component is initialized. Useful for setup tasks.
import { Component, ViewChild } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuComponent, MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<button (click)="addNewItem()">Add Item</button>
<ejs-menu #menu [items]="menuItems" (created)="onCreated()"></ejs-menu>
</div>
`
})
export class AppComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [{ text: 'Open' }, { text: 'Save' }]
}
];
public onCreated(): void {
console.log('Menu initialized');
// Perform initialization tasks
this.disableDefaultItems();
}
private disableDefaultItems(): void {
this.menuObj?.enableItems(['Save'], false, false);
}
public addNewItem(): void {
const newItems: MenuItemModel[] = [{ text: 'New File' }];
this.menuObj?.insertAfter(newItems, 'File', false);
}
}beforeItemRender Event
Triggered before each menu item is rendered. Allows customization of individual menu items, such as setting tooltips, titles, or attributes.
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems" (beforeItemRender)="beforeItemRender($event)"></ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
id: 'settingsIcon',
iconCss: 'e-icons e-settings',
items: [
{ text: 'Open', items: [
{ text: 'Sub Option 1' },
{ text: 'Sub Option 2' }
]},
{ text: 'Save' },
{ separator: true },
{ text: 'Exit' }
]
},
{
id: 'userIcon',
iconCss: 'e-icons e-user',
items: [
{ text: 'Profile' },
{ text: 'Account' }
]
}
];
public beforeItemRender(args: MenuEventArgs): void {
// Set title attribute for icon-based menu items to show tooltip
if (args.item?.id === 'settingsIcon') {
args.element?.setAttribute('title', 'Settings');
} else if (args.item?.id === 'userIcon') {
args.element?.setAttribute('title', 'User Account');
}
// Add custom CSS class for styling
if (args.item?.text === 'Exit') {
args.element?.classList.add('menu-item-danger');
}
// Add data attributes
if (args.item?.text) {
args.element?.setAttribute('data-menu-item', args.item.text);
}
}
}Use Cases
- Set Tooltips for icon-only menu items
- Add Title Attributes for accessibility
- Apply Custom Styling based on item properties
- Set Data Attributes for tracking or testing
- Add ARIA Labels for screen readers
- Customize DOM Elements before rendering
Setting Tooltips for Icon Menu Items
public beforeItemRender(args: MenuEventArgs): void {
const item = args.item;
// Define icon to tooltip mapping
const tooltipMap: { [key: string]: string } = {
'settingsIcon': 'Settings',
'userIcon': 'User Profile',
'helpIcon': 'Help & Support',
'logoutIcon': 'Logout'
};
if (item?.id && item.id in tooltipMap) {
args.element?.setAttribute('title', tooltipMap[item.id]);
}
}Adding ARIA Labels
public beforeItemRender(args: MenuEventArgs): void {
if (args.item?.id === 'settingsIcon') {
args.element?.setAttribute('aria-label', 'Settings Menu');
args.element?.setAttribute('role', 'menuitem');
}
}Custom Styling Based on Content
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel, MenuEventArgs } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems" (beforeItemRender)="beforeItemRender($event)"></ejs-menu>
</div>
`,
styles: [`
.menu-item-danger {
color: #e74c3c !important;
}
.menu-item-admin {
background-color: #fffacd;
font-weight: bold;
}
.menu-item-disabled-style {
opacity: 0.6;
cursor: not-allowed;
}
`]
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Save' },
{ text: 'Delete' },
{ text: 'Admin Reset', id: 'admin-reset' }
]
}
];
public beforeItemRender(args: MenuEventArgs): void {
// Style dangerous actions
if (args.item?.text === 'Delete') {
args.element?.classList.add('menu-item-danger');
}
// Highlight admin-only options
if (args.item?.id === 'admin-reset') {
args.element?.classList.add('menu-item-admin');
args.element?.setAttribute('title', 'Admin only: Reset all settings');
}
// Disable certain items visually
if (args.item?.text === 'Archive') {
args.element?.classList.add('menu-item-disabled-style');
}
}
}Complete Set Title & Icon Example
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-title-icon',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems" (beforeItemRender)="beforeItemRender($event)"></ejs-menu>
</div>
`
})
export class TitleIconComponent {
public menuItems: MenuItemModel[] = [
{
id: 'home',
iconCss: 'e-icons e-home'
},
{
id: 'settings',
iconCss: 'e-icons e-settings',
items: [
{ text: 'General Settings' },
{ text: 'Advanced' },
{ text: 'Security' }
]
},
{
id: 'help',
iconCss: 'e-icons e-help'
},
{
id: 'logout',
iconCss: 'e-icons e-logout'
}
];
public beforeItemRender(args: MenuEventArgs): void {
const titleMap: { [key: string]: string } = {
'home': 'Home',
'settings': 'Settings',
'help': 'Help & Support',
'logout': 'Sign Out'
};
if (args.item?.id && args.item.id in titleMap) {
args.element?.setAttribute('title', titleMap[args.item.id]);
args.element?.setAttribute('aria-label', titleMap[args.item.id]);
}
}
}Practical Examples
Example 1: Event Trace Logger
Log all events for debugging:
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<div class="event-trace" [innerHTML]="eventTrace"></div>
<button (click)="clearLog()">Clear Log</button>
<ejs-menu
[items]="menuItems"
(beforeOpen)="beforeOpen($event)"
(beforeClose)="beforeClose($event)"
(onOpen)="onOpen($event)"
(onClose)="onClose($event)"
(select)="onItemSelect($event)"
(created)="onCreated()">
</ejs-menu>
</div>
`
})
export class AppComponent {
public eventTrace: string = '';
public menuItems: MenuItemModel[] = [
{
text: 'Events',
items: [
{ text: 'Conferences' },
{ text: 'Music' }
]
},
{
text: 'Movies',
items: [
{ text: 'Now Showing' },
{ text: 'Coming Soon' }
]
}
];
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
this.addToLog('beforeOpen: ' + args.items?.[0]?.text);
}
public beforeClose(args: BeforeOpenCloseMenuEventArgs): void {
this.addToLog('beforeClose: ' + args.items?.[0]?.text);
}
public onOpen(args: OpenCloseMenuEventArgs): void {
this.addToLog('onOpen: ' + args.items?.[0]?.text);
}
public onClose(args: OpenCloseMenuEventArgs): void {
this.addToLog('onClose: ' + args.items?.[0]?.text);
}
public onItemSelect(args: MenuEventArgs): void {
this.addToLog('select: ' + args.item?.text);
}
public onCreated(): void {
this.addToLog('created: Menu initialized');
}
private addToLog(message: string): void {
this.eventTrace = this.eventTrace + message + '<br />';
}
public clearLog(): void {
this.eventTrace = '';
}
}Example 2: Dynamic Item Enable/Disable
public beforeOpen(args: BeforeOpenCloseMenuEventArgs): void {
// Dynamically disable submenu items before opening
for (let i = 0; i < args.items.length; i++) {
if (args.items[i].text === 'Admin Only') {
if (!this.isAdmin()) {
this.menuObj?.enableItems([args.items[i].text as string], false, false);
}
}
}
}
private isAdmin(): boolean {
// Check user role
return false;
}Example 3: Custom Navigation
public onItemSelect(args: MenuEventArgs): void {
const selectedText = args.item?.text;
const url = args.item?.url;
if (url) {
// Navigate to URL
window.location.href = url;
} else {
// Handle custom logic
switch (selectedText) {
case 'Dashboard':
this.navigateTo('/dashboard');
break;
case 'Settings':
this.navigateTo('/settings');
break;
default:
console.log('Selected:', selectedText);
}
}
}
private navigateTo(path: string): void {
console.log('Navigating to:', path);
// Use Angular Router for navigation
}---
Next: To style and theme your menu, see references/styling-and-appearance.md.
Getting Started with Syncfusion Angular Menu
Table of Contents
- Setup and Installation
- Create Angular Application
- Install Menu Package
- Add Menu Component
- CSS Configuration
- Basic Menu Example
- Menu with Separators
- Essential Properties
- Running the Application
Setup and Installation
Angular CLI Installation
Use Angular CLI to set up your Angular applications. Install Angular CLI globally:
npm install -g @angular/cliTo install a specific version:
npm install -g @angular/cli@21.0.0Note: For Angular 21 and above, standalone components are the default architecture.
Create Angular Application
Generate a new Angular application with Angular CLI:
ng new syncfusion-angular-appThis command will prompt you to configure settings:
- Enable Angular routing (optional)
- Choose stylesheet format (CSS, SCSS, Less, etc.)
- Configure Server-side rendering (SSR) if needed
- Select AI tool integration (optional)
For SCSS support:
ng new syncfusion-angular-app --style=scssNavigate to your project:
cd syncfusion-angular-appInstall Menu Package
Ivy Library Distribution Package
Syncfusion Angular packages (version 20.2.36+) use the Ivy distribution format and are compatible with Angular 12 and above. Install the navigations package:
npm install @syncfusion/ej2-angular-navigations --saveLegacy Angular Compatibility Compiler (ngcc)
For Angular version below 12, use the ngcc package:
npm install @syncfusion/ej2-angular-navigations@ngcc --saveUpdate your package.json:
"@syncfusion/ej2-angular-navigations": "20.2.38-ngcc"Note: If ngcc tag is not specified, the Ivy library will be installed and may show a warning.
Dependencies Structure
The Menu package includes the following dependencies:
@syncfusion/ej2-angular-navigations
├── @syncfusion/ej2-angular-base
├── @syncfusion/ej2-navigations
│ ├── @syncfusion/ej2-base
│ ├── @syncfusion/ej2-data
│ ├── @syncfusion/ej2-lists
│ ├── @syncfusion/ej2-inputs
│ ├── @syncfusion/ej2-splitbuttons
│ └── @syncfusion/ej2-popups
│ └── @syncfusion/ej2-buttonsAdd Menu Component
Basic Setup
Modify app.component.ts to render the Menu component using the ejs-menu directive. Import the required modules:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems"></ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{
text: 'View',
items: [
{ text: 'Toolbar' },
{ text: 'Sidebar' }
]
},
{
text: 'Tools',
items: [
{ text: 'Spelling & Grammar' },
{ text: 'Customize' },
{ text: 'Options' }
]
},
{ text: 'Go' },
{ text: 'Help' }
];
}Bootstrap Application
main.ts:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));CSS Configuration
Add Menu component styles to your stylesheet. In styles.css:
@import '../node_modules/@syncfusion/ej2-base/styles/material3.css';
@import '../node_modules/@syncfusion/ej2-navigations/styles/material3.css';Available themes:
material3.css- Material Design 3material.css- Material Designbootstrap5.css- Bootstrap 5fluent2.css- Fluent 2tailwind.css- Tailwind CSS
Basic Menu Example
Complete example with TypeScript, template, and bootstrap code:
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
import { enableRipple } from '@syncfusion/ej2-base';
enableRipple(true);
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-root',
template: `
<div class="e-section-control">
<ejs-menu [items]="menuItems"></ejs-menu>
</div>
`
})
export class AppComponent {
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{
text: 'View',
items: [
{ text: 'Toolbar' },
{ text: 'Sidebar' },
{ text: 'Full Screen' }
]
},
{
text: 'Tools',
items: [
{ text: 'Spelling & Grammar' },
{ text: 'Customize' },
{ text: 'Options' }
]
},
{ text: 'Go' },
{ text: 'Help' }
];
}Bootstrap in main.ts:
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));Menu with Separators
Separators are horizontal lines used to group menu items. Use the separator property:
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'Open' },
{ text: 'Save' },
{ separator: true },
{ text: 'Exit' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{
text: 'View',
items: [
{ text: 'Toolbar' },
{ text: 'Sidebar' },
{ text: 'Full Screen' }
]
},
{
text: 'Tools',
items: [
{ text: 'Spelling & Grammar' },
{ text: 'Customize' },
{ text: 'Options' }
]
},
{ text: 'Go' },
{ text: 'Help' }
];Note: The separator property should not be used with other field properties like text or items in the same menu item.
You can also enable separators for horizontal menu items to group them visually.
Essential Properties
Beyond the basic items array, the Menu component supports several important properties. Here are the most essential ones to get started:
enablePersistence - State Persistence
Save and restore menu state between page reloads using localStorage:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[enablePersistence]="true">
</ejs-menu>
`
})
export class MenuComponent { }When enabled, if a user opens submenu items, closes the browser, and returns later, the same submenus will remain open.
enableRtl - Right-to-Left Language Support
Enable RTL layout for Arabic, Hebrew, Persian, and other RTL languages:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[enableRtl]="true">
</ejs-menu>
`
})
export class MenuComponent { }Or toggle dynamically:
@ViewChild('menu')
public menuObj?: MenuComponent;
public toggleRtl(): void {
this.menuObj!.enableRtl = !this.menuObj!.enableRtl;
}cssClass - Custom Styling
Add custom CSS classes to the menu wrapper:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[cssClass]="'dark-theme gradient-bg custom-menu'">
</ejs-menu>
`,
styles: [`
:host ::ng-deep .dark-theme.e-menu {
background: #2c3e50;
color: #ecf0f1;
}
:host ::ng-deep .dark-theme.e-menu-item:hover {
background: #34495e;
}
`]
})
export class MenuComponent { }hoverDelay - Control Submenu Open Delay
Set delay (in milliseconds) before submenu opens on hover. Useful to prevent accidental opening:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[hoverDelay]="400">
</ejs-menu>
`
})
export class MenuComponent { }With 400ms delay, submenus open after 400ms of hovering over an item.
enableScrolling - Large Menu Support
Enable scrollbar when menu items exceed available space:
@Component({
template: `
<ejs-menu
[items]="largeMenuItems"
[enableScrolling]="true"
[cssClass]="'scrollable-menu'"
style="height: 300px;">
</ejs-menu>
`,
styles: [`
:host ::ng-deep .scrollable-menu {
max-height: 300px;
overflow-y: auto;
}
`]
})
export class MenuComponent {
public largeMenuItems: MenuItemModel[] = [
// ... 50+ items
];
}locale - Localization
Set UI language/culture:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[locale]="'es-ES'">
</ejs-menu>
`
})
export class MenuComponent { }Supported locales: en-US (default), es-ES, fr-FR, de-DE, ar-AE, ja-JP, zh-CN, etc.
Running the Application
Start your application:
ng serveThe application will be available at http://localhost:4200/.
---
Next: Once your basic menu is set up, explore data binding and templates in references/data-source-binding.md for dynamic menu population.
Hamburger Mode and Responsive Menu
Table of Contents
- Overview
- Hamburger Mode Properties
- Basic Hamburger Implementation
- Advanced Hamburger Patterns
- Mobile Responsive Menu
- State Persistence
- Best Practices
---
Overview
Hamburger mode transforms the menu into a collapsible drawer menu, ideal for mobile and responsive layouts. When enabled, menu items are hidden by default and revealed through a toggle button (hamburger icon).
Key Features:
- Collapsible menu drawer
- Mobile-friendly interaction
- Toggle animation support
- Responsive design ready
- Touch-friendly interface
---
Hamburger Mode Properties
Core Properties
| Property | Type | Purpose |
|---|---|---|
hamburgerMode | boolean | Enables/disables hamburger mode |
target | string | Selector for toggle button element |
title | string | Title displayed in hamburger drawer |
Example Configuration
@Component({
template: `
<ejs-menu
[items]="menuItems"
[hamburgerMode]="true"
[target]="'#menu-toggle'"
[title]="'Navigation'">
</ejs-menu>
`
})
export class MenuComponent { }---
Basic Hamburger Implementation
Simple Hamburger Menu
import { Component } from '@angular/core';
import { MenuModule } from '@syncfusion/ej2-angular-navigations';
import { MenuItemModel } from '@syncfusion/ej2-angular-navigations';
@Component({
imports: [MenuModule],
standalone: true,
selector: 'app-hamburger-menu',
template: `
<header class="app-header">
<div class="header-content">
<h1 class="app-title">My App</h1>
<button
id="menu-toggle"
class="hamburger-btn"
title="Toggle Menu">
<span class="hamburger-icon">☰</span>
</button>
</div>
</header>
<ejs-menu
#menu
[items]="menuItems"
[hamburgerMode]="true"
[target]="'#menu-toggle'"
[title]="'Menu'"
[cssClass]="'mobile-menu'">
</ejs-menu>
`,
styles: [`
:host ::ng-deep .app-header {
background: #2c3e50;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
:host ::ng-deep .header-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
:host ::ng-deep .hamburger-btn {
background: none;
border: none;
color: white;
font-size: 1.5rem;
cursor: pointer;
padding: 0.5rem;
}
:host ::ng-deep .hamburger-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
:host ::ng-deep .mobile-menu.e-menu {
background: #34495e;
color: white;
}
:host ::ng-deep .mobile-menu.e-menu-item:hover {
background: #2c3e50;
}
`]
})
export class HamburgerMenuComponent {
public menuItems: MenuItemModel[] = [
{
text: 'Home',
iconCss: 'e-icons e-home',
url: '/'
},
{
text: 'Products',
iconCss: 'e-icons e-shopping',
items: [
{ text: 'Electronics', url: '/products/electronics' },
{ text: 'Books', url: '/products/books' },
{ text: 'Clothing', url: '/products/clothing' }
]
},
{
text: 'Services',
iconCss: 'e-icons e-settings',
items: [
{ text: 'Consulting', url: '/services/consulting' },
{ text: 'Support', url: '/services/support' },
{ text: 'Training', url: '/services/training' }
]
},
{
text: 'About Us',
iconCss: 'e-icons e-info',
url: '/about'
},
{
text: 'Contact',
iconCss: 'e-icons e-mail',
url: '/contact'
}
];
}---
Advanced Hamburger Patterns
Responsive Toggle (Desktop/Mobile)
import { Component, HostListener } from '@angular/core';
import { MenuComponent } from '@syncfusion/ej2-angular-navigations';
import { ViewChild } from '@angular/core';
@Component({
template: `
<div [class.desktop-view]="isDesktop">
<button
*ngIf="!isDesktop"
id="menu-toggle"
class="hamburger-btn"
(click)="toggleMobileMenu()">
☰ Menu
</button>
<ejs-menu
#menu
[items]="menuItems"
[hamburgerMode]="!isDesktop"
[target]="'#menu-toggle'"
[title]="'Menu'"
[orientation]="isDesktop ? 'Horizontal' : 'Vertical'">
</ejs-menu>
</div>
`,
styles: [`
:host.desktop-view ::ng-deep .e-menu {
display: block;
}
:host ::ng-deep .hamburger-btn {
display: block;
background: #2c3e50;
color: white;
border: none;
padding: 0.75rem 1rem;
cursor: pointer;
font-size: 1rem;
border-radius: 4px;
}
:host ::ng-deep .hamburger-btn:hover {
background: #34495e;
}
`]
})
export class ResponsiveMenuComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public isDesktop: boolean = window.innerWidth > 768;
public menuItems: MenuItemModel[] = [
// ... menu items
];
@HostListener('window:resize', ['$event'])
public onWindowResize(event: Event): void {
const width = (event.target as Window).innerWidth;
this.isDesktop = width > 768;
}
public toggleMobileMenu(): void {
// Toggle mobile menu visibility
console.log('Menu toggled');
}
}Hamburger with Animation
import { Component } from '@angular/core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@Component({
template: `
<header [@headerSlide]="isMenuOpen ? 'open' : 'closed'">
<button
id="menu-toggle"
(click)="isMenuOpen = !isMenuOpen"
[@hamburgerRotate]="isMenuOpen ? 'rotated' : 'normal'">
☰
</button>
</header>
<ejs-menu
#menu
[items]="menuItems"
[hamburgerMode]="true"
[target]="'#menu-toggle'"
[@slideIn]="isMenuOpen ? 'in' : 'out'">
</ejs-menu>
`,
animations: [
trigger('hamburgerRotate', [
state('normal', style({ transform: 'rotate(0deg)' })),
state('rotated', style({ transform: 'rotate(90deg)' })),
transition('normal <=> rotated', animate('300ms ease-in-out'))
]),
trigger('slideIn', [
state('in', style({ transform: 'translateX(0)', opacity: 1 })),
state('out', style({ transform: 'translateX(-100%)', opacity: 0 })),
transition('in <=> out', animate('300ms ease-in-out'))
]),
trigger('headerSlide', [
state('open', style({ boxShadow: '0 2px 8px rgba(0,0,0,0.1)' })),
state('closed', style({ boxShadow: 'none' })),
transition('open <=> closed', animate('300ms ease-in-out'))
])
]
})
export class AnimatedHamburgerComponent {
public isMenuOpen: boolean = false;
public menuItems: MenuItemModel[] = [
// ... menu items
];
}---
Mobile Responsive Menu
Complete Mobile Menu Solution
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
imports: [MenuModule, CommonModule],
standalone: true,
selector: 'app-mobile-menu',
template: `
<div class="mobile-container">
<!-- Mobile Header -->
<header class="mobile-header">
<button
id="menu-toggle"
class="menu-trigger"
(click)="toggleMenu()">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</button>
<h1 class="mobile-title">App Name</h1>
<button class="user-menu" (click)="toggleUserMenu()">
<span class="user-icon">👤</span>
</button>
</header>
<!-- Mobile Menu -->
<div class="menu-wrapper" [class.open]="menuOpen">
<ejs-menu
#menu
[items]="menuItems"
[hamburgerMode]="true"
[target]="'#menu-toggle'"
[enablePersistence]="true"
[cssClass]="'mobile-menu'">
</ejs-menu>
</div>
<!-- Main Content -->
<main class="mobile-content">
<p>Your content here</p>
</main>
</div>
`,
styles: [`
:host ::ng-deep .mobile-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
:host ::ng-deep .mobile-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
:host ::ng-deep .menu-trigger {
background: none;
border: none;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 4px;
padding: 0.5rem;
}
:host ::ng-deep .hamburger-line {
width: 24px;
height: 2px;
background: white;
transition: all 0.3s ease;
}
:host ::ng-deep .mobile-title {
flex: 1;
text-align: center;
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
:host ::ng-deep .user-menu {
background: rgba(255, 255, 255, 0.2);
border: 1px solid white;
color: white;
border-radius: 50%;
width: 40px;
height: 40px;
cursor: pointer;
font-size: 1.25rem;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
:host ::ng-deep .user-menu:active {
background: rgba(255, 255, 255, 0.3);
}
:host ::ng-deep .menu-wrapper {
position: fixed;
top: 57px;
left: -300px;
width: 300px;
height: calc(100vh - 57px);
background: white;
transition: left 0.3s ease;
z-index: 99;
overflow-y: auto;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
}
:host ::ng-deep .menu-wrapper.open {
left: 0;
}
:host ::ng-deep .mobile-menu.e-menu {
border: none;
box-shadow: none;
}
:host ::ng-deep .mobile-menu.e-menu-item {
padding: 0.75rem 1rem;
border-bottom: 1px solid #f0f0f0;
}
:host ::ng-deep .mobile-menu.e-menu-item:hover {
background: #f8f9fa;
}
:host ::ng-deep .mobile-content {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
@media (max-width: 480px) {
:host ::ng-deep .mobile-title {
font-size: 1rem;
}
:host ::ng-deep .menu-wrapper {
width: 80vw;
left: -80vw;
}
}
`]
})
export class MobileMenuComponent implements OnInit {
public menuOpen: boolean = false;
public menuItems: MenuItemModel[] = [
{
text: 'Dashboard',
iconCss: 'e-icons e-home',
url: '/dashboard'
},
{
text: 'Profile',
iconCss: 'e-icons e-user',
items: [
{ text: 'View Profile', url: '/profile' },
{ text: 'Edit Profile', url: '/profile/edit' },
{ text: 'Settings', url: '/settings' }
]
},
{
text: 'Notifications',
iconCss: 'e-icons e-bell',
url: '/notifications'
},
{
text: 'Support',
iconCss: 'e-icons e-help',
items: [
{ text: 'Help Center', url: '/help' },
{ text: 'Contact Us', url: '/contact' },
{ text: 'FAQ', url: '/faq' }
]
},
{ separator: true },
{
text: 'Logout',
iconCss: 'e-icons e-logout',
url: '/logout'
}
];
ngOnInit(): void {
// Close menu on outside click
document.addEventListener('click', (e) => {
if (!(e.target as HTMLElement).closest('#menu-toggle') &&
!(e.target as HTMLElement).closest('.menu-wrapper')) {
this.menuOpen = false;
}
});
}
public toggleMenu(): void {
this.menuOpen = !this.menuOpen;
}
public toggleUserMenu(): void {
console.log('User menu clicked');
}
}---
State Persistence
Persistent Hamburger Menu State
@Component({
template: `
<ejs-menu
[items]="menuItems"
[hamburgerMode]="true"
[target]="'#menu-toggle'"
[enablePersistence]="true"
[cssClass]="'persistent-menu'">
</ejs-menu>
`
})
export class PersistentMenuComponent {
public menuItems: MenuItemModel[] = [
// ... menu items
];
}How It Works: 1. User opens submenu items 2. State is automatically saved to localStorage 3. User closes browser 4. User returns to site 5. Previous menu state is restored (same items remain open)
localStorage Key: ejs_persist_<MenuId>
---
Best Practices
1. Accessibility
<button
id="menu-toggle"
aria-label="Toggle navigation menu"
aria-expanded="false"
(click)="toggleMenu()">
☰
</button>
<ejs-menu
[items]="menuItems"
[hamburgerMode]="true"
role="navigation"
aria-label="Main navigation">
</ejs-menu>2. Performance
- Use
enablePersistenceto reduce re-renders - Lazy load submenu items if needed
- Avoid excessive animations on mobile
3. User Experience
- Keep menu items to 5-7 top level
- Use clear icons with text labels
- Provide visual feedback on clicks
- Ensure adequate touch target size (44x44 px minimum)
4. Responsive Breakpoints
@HostListener('window:resize', ['$event'])
public onResize(event: Event): void {
const width = window.innerWidth;
if (width > 1024) {
// Desktop: Horizontal menu
this.showHorizontal();
} else if (width > 768) {
// Tablet: Vertical sidebar
this.showVerticalSidebar();
} else {
// Mobile: Hamburger
this.showHamburger();
}
}---
Next: For advanced state management and scrolling features, see references/advanced-features.md.
Internationalization, Persistence, and Advanced Configuration
Table of Contents
- Localization (i18n)
- State Persistence
- HTML Sanitization
- Hover Delay Control
- Custom CSS Classes
- Advanced Configuration Patterns
---
Localization (i18n)
The Menu component supports multiple languages through the locale property. This controls UI text and RTL layouts.
Supported Locales
| Locale Code | Language | Script |
|---|---|---|
en-US | English (US) | LTR |
en-GB | English (GB) | LTR |
de-DE | German | LTR |
fr-FR | French | LTR |
es-ES | Spanish | LTR |
it-IT | Italian | LTR |
pt-BR | Portuguese (Brazil) | LTR |
ru-RU | Russian | LTR |
ar-AE | Arabic (UAE) | RTL |
ar-SA | Arabic (Saudi Arabia) | RTL |
he-IL | Hebrew | RTL |
ja-JP | Japanese | LTR |
zh-CN | Chinese (Simplified) | LTR |
zh-TW | Chinese (Traditional) | LTR |
ko-KR | Korean | LTR |
th-TH | Thai | LTR |
Basic Localization
import { Component } from '@angular/core';
@Component({
template: `
<div>
<label>Select Language:</label>
<select (change)="changeLocale($event)">
<option value="en-US">English</option>
<option value="de-DE">Deutsch</option>
<option value="fr-FR">Français</option>
<option value="es-ES">Español</option>
<option value="ar-AE">العربية</option>
</select>
</div>
<ejs-menu
#menu
[items]="menuItems"
[locale]="currentLocale"
[enableRtl]="isRtlLocale()">
</ejs-menu>
`
})
export class LocalizationComponent {
public currentLocale: string = 'en-US';
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]
},
{
text: 'Edit',
items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]
},
{ text: 'Help' }
];
public changeLocale(event: Event): void {
const selectElement = event.target as HTMLSelectElement;
this.currentLocale = selectElement.value;
}
public isRtlLocale(): boolean {
const rtlLocales = ['ar-AE', 'ar-SA', 'he-IL', 'fa-IR'];
return rtlLocales.includes(this.currentLocale);
}
}Localized Menu Items
Create different menu items for different languages:
@Component({
template: `
<ejs-menu
[items]="getMenuItemsForLocale()"
[locale]="currentLocale"
[enableRtl]="isRtlLocale()">
</ejs-menu>
`
})
export class LocalizedMenuComponent {
public currentLocale: string = 'en-US';
private translations = {
'en-US': {
file: 'File',
edit: 'Edit',
view: 'View',
new: 'New',
open: 'Open',
save: 'Save',
cut: 'Cut',
copy: 'Copy',
paste: 'Paste'
},
'de-DE': {
file: 'Datei',
edit: 'Bearbeiten',
view: 'Ansicht',
new: 'Neu',
open: 'Öffnen',
save: 'Speichern',
cut: 'Ausschneiden',
copy: 'Kopieren',
paste: 'Einfügen'
},
'fr-FR': {
file: 'Fichier',
edit: 'Édition',
view: 'Affichage',
new: 'Nouveau',
open: 'Ouvrir',
save: 'Enregistrer',
cut: 'Couper',
copy: 'Copier',
paste: 'Coller'
},
'es-ES': {
file: 'Archivo',
edit: 'Editar',
view: 'Ver',
new: 'Nuevo',
open: 'Abrir',
save: 'Guardar',
cut: 'Cortar',
copy: 'Copiar',
paste: 'Pegar'
}
};
public getMenuItemsForLocale(): MenuItemModel[] {
const t = this.translations[this.currentLocale as keyof typeof this.translations]
|| this.translations['en-US'];
return [
{
text: t.file,
items: [
{ text: t.new },
{ text: t.open },
{ text: t.save }
]
},
{
text: t.edit,
items: [
{ text: t.cut },
{ text: t.copy },
{ text: t.paste }
]
},
{ text: t.view }
];
}
public isRtlLocale(): boolean {
const rtlLocales = ['ar-AE', 'ar-SA', 'he-IL'];
return rtlLocales.includes(this.currentLocale);
}
}RTL (Right-to-Left) Languages
Automatic RTL support for Arabic, Hebrew, Persian:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[locale]="'ar-AE'"
[enableRtl]="true"
dir="rtl">
</ejs-menu>
`,
styles: [`
:host {
direction: rtl;
}
`]
})
export class ArabicMenuComponent {
public menuItems: MenuItemModel[] = [
{
text: 'ملف',
items: [
{ text: 'جديد' },
{ text: 'فتح' },
{ text: 'حفظ' }
]
},
{
text: 'تحرير',
items: [
{ text: 'قص' },
{ text: 'نسخ' },
{ text: 'لصق' }
]
}
];
}---
State Persistence
enablePersistence Property
Persists menu state (open/closed submenus) to localStorage:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[enablePersistence]="true">
</ejs-menu>
`
})
export class PersistentMenuComponent {
public menuItems: MenuItemModel[] = [
// ... menu items
];
}What Gets Persisted:
- Open/closed state of submenus
- Last selected item
- Scroll position
Storage Key: ej2_<ComponentId>_persist
Programmatic State Management
@Component({
template: `
<button (click)="saveMenuState()">Save State</button>
<button (click)="restoreMenuState()">Restore State</button>
<button (click)="clearMenuState()">Clear State</button>
<ejs-menu
#menu
[items]="menuItems"
[enablePersistence]="enablePersistence">
</ejs-menu>
`
})
export class MenuStateComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public enablePersistence: boolean = true;
public menuItems: MenuItemModel[] = [
// ... menu items
];
public saveMenuState(): void {
const state = this.getMenuState();
localStorage.setItem('custom_menu_state', JSON.stringify(state));
console.log('Menu state saved', state);
}
public restoreMenuState(): void {
const savedState = localStorage.getItem('custom_menu_state');
if (savedState) {
const state = JSON.parse(savedState);
this.applyMenuState(state);
console.log('Menu state restored', state);
}
}
public clearMenuState(): void {
localStorage.removeItem('custom_menu_state');
localStorage.removeItem('ej2_menu_persist');
console.log('Menu state cleared');
}
private getMenuState(): any {
// Extract menu state (custom implementation)
return {
timestamp: new Date().toISOString(),
locale: 'en-US'
};
}
private applyMenuState(state: any): void {
// Apply saved state (custom implementation)
console.log('Applying state:', state);
}
}---
HTML Sanitization
enableHtmlSanitizer Property
Controls sanitization of HTML content to prevent XSS attacks:
@Component({
template: `
<div>
<label>
<input type="checkbox" [checked]="sanitizeHtml" (change)="toggleSanitizer()">
Enable HTML Sanitizer (Security)
</label>
</div>
<ejs-menu
[items]="menuItems"
[enableHtmlSanitizer]="sanitizeHtml">
</ejs-menu>
`
})
export class SanitizationComponent {
public sanitizeHtml: boolean = true;
public menuItems: MenuItemModel[] = [
{
text: 'Safe Content',
htmlAttributes: {
title: 'This is <b>safe</b> HTML'
}
},
{
text: 'With Script',
htmlAttributes: {
// Sanitized: script tags removed
title: 'This will remove <script>alert("xss")</script> tags'
}
}
];
public toggleSanitizer(): void {
this.sanitizeHtml = !this.sanitizeHtml;
}
}Security Note:
- ✅ Default (true): Sanitizes suspicious HTML/scripts
- ⚠️ false: Allows raw HTML (use only with trusted sources)
Safe HTML Content
// Always safe - plain text
{ text: 'File', items: [...] }
// Safe with properties
{ text: 'Edit', url: '/edit', iconCss: 'e-icons e-edit' }
// Use templates for complex content
@Component({
template: `
<ejs-menu [items]="menuItems">
<ng-template #itemTemplate let-data="data">
<span [innerHTML]="data.customHtml"></span>
</ng-template>
</ejs-menu>
`
})---
Hover Delay Control
hoverDelay Property
Controls millisecond delay before submenu opens on hover:
@Component({
template: `
<div>
<label>Hover Delay: {{ hoverDelay }}ms</label>
<input
type="range"
min="0"
max="1000"
step="100"
[(ngModel)]="hoverDelay"
(change)="applyHoverDelay()">
</div>
<ejs-menu
#menu
[items]="menuItems"
[hoverDelay]="hoverDelay">
</ejs-menu>
`
})
export class HoverDelayComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public hoverDelay: number = 0;
public menuItems: MenuItemModel[] = [
{
text: 'File',
items: [
{ text: 'New' },
{ text: 'Open' }
]
}
];
public applyHoverDelay(): void {
if (this.menuObj) {
this.menuObj.hoverDelay = this.hoverDelay;
}
}
}Common Values:
0- Instant (default, faster desktop experience)200- Quick (200ms delay)300- Moderate (300ms delay, prevents accidental opens)500- Slow (500ms delay, more deliberate)
---
Custom CSS Classes
cssClass Property
Apply custom CSS classes for extensive styling:
@Component({
template: `
<ejs-menu
[items]="menuItems"
[cssClass]="'dark-theme gradient-bg custom-menu'">
</ejs-menu>
`,
styles: [`
:host ::ng-deep .dark-theme.e-menu {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #ffffff;
}
:host ::ng-deep .dark-theme.e-menu-item {
color: #ffffff;
transition: all 0.3s ease;
}
:host ::ng-deep .dark-theme.e-menu-item:hover {
background: rgba(255, 255, 255, 0.1);
transform: translateX(4px);
}
:host ::ng-deep .dark-theme.e-menu-item:active {
background: rgba(255, 255, 255, 0.2);
}
:host ::ng-deep .gradient-bg .e-ul {
background: linear-gradient(180deg, rgba(26, 26, 46, 0.95), rgba(22, 33, 62, 0.95));
}
:host ::ng-deep .custom-menu .e-icons {
margin-right: 8px;
}
`]
})
export class CustomThemeComponent {
public menuItems: MenuItemModel[] = [
{ text: 'File', iconCss: 'e-icons e-file' },
{ text: 'Edit', iconCss: 'e-icons e-edit' },
{ text: 'Help', iconCss: 'e-icons e-help' }
];
}---
Advanced Configuration Patterns
Complete Advanced Setup
@Component({
imports: [MenuModule, CommonModule],
standalone: true,
selector: 'app-advanced-menu',
template: `
<div class="menu-config">
<div class="config-panel">
<h3>Configuration</h3>
<label>
Locale:
<select [(ngModel)]="locale" (change)="applyConfig()">
<option value="en-US">English</option>
<option value="de-DE">Deutsch</option>
<option value="ar-AE">العربية</option>
</select>
</label>
<label>
<input type="checkbox" [(ngModel)]="enableRtl" (change)="applyConfig()">
Enable RTL
</label>
<label>
<input type="checkbox" [(ngModel)]="enablePersistence" (change)="applyConfig()">
Enable Persistence
</label>
<label>
<input type="checkbox" [(ngModel)]="enableSanitizer" (change)="applyConfig()">
Enable HTML Sanitizer
</label>
<label>
Hover Delay: {{ hoverDelay }}ms
<input
type="range"
min="0"
max="1000"
step="100"
[(ngModel)]="hoverDelay"
(change)="applyConfig()">
</label>
<label>
CSS Theme:
<select [(ngModel)]="selectedTheme" (change)="applyConfig()">
<option value="light-theme">Light</option>
<option value="dark-theme">Dark</option>
<option value="material-theme">Material</option>
</select>
</label>
</div>
<ejs-menu
#menu
[items]="menuItems"
[locale]="locale"
[enableRtl]="enableRtl"
[enablePersistence]="enablePersistence"
[enableHtmlSanitizer]="enableSanitizer"
[hoverDelay]="hoverDelay"
[cssClass]="selectedTheme">
</ejs-menu>
</div>
`,
styles: [`
:host ::ng-deep .menu-config {
display: flex;
gap: 2rem;
}
:host ::ng-deep .config-panel {
flex: 1;
padding: 1rem;
background: #f5f5f5;
border-radius: 4px;
}
:host ::ng-deep .config-panel label {
display: flex;
flex-direction: column;
margin-bottom: 1rem;
font-weight: 500;
}
:host ::ng-deep .config-panel input,
:host ::ng-deep .config-panel select {
margin-top: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
:host ::ng-deep .dark-theme.e-menu {
background: #2c3e50;
color: white;
}
:host ::ng-deep .material-theme.e-menu {
background: #e8eaf6;
border: 1px solid #5e35b1;
}
`]
})
export class AdvancedMenuComponent {
@ViewChild('menu')
public menuObj?: MenuComponent;
public locale: string = 'en-US';
public enableRtl: boolean = false;
public enablePersistence: boolean = true;
public enableSanitizer: boolean = true;
public hoverDelay: number = 0;
public selectedTheme: string = 'light-theme';
public menuItems: MenuItemModel[] = [
{ text: 'File', items: [
{ text: 'New' },
{ text: 'Open' },
{ text: 'Save' }
]},
{ text: 'Edit', items: [
{ text: 'Cut' },
{ text: 'Copy' },
{ text: 'Paste' }
]},
{ text: 'Help' }
];
public applyConfig(): void {
console.log('Configuration applied');
console.log({
locale: this.locale,
rtl: this.enableRtl,
persistence: this.enablePersistence,
sanitizer: this.enableSanitizer,
hoverDelay: this.hoverDelay,
theme: this.selectedTheme
});
}
}---
Next: Explore comprehensive API documentation in references/api-reference.md for all properties, methods, and events.