寻求Angular独立下拉组件:替代Material的mat-menu方案
解决方案:独立Angular下拉菜单组件实现
一、移除mat-menu依赖,实现自定义触发逻辑
完全替代mat-menu的hover/点击触发逻辑,无需依赖任何第三方框架:
- 点击触发:绑定
(click)事件切换菜单显示状态,通过HostListener监听文档点击事件,实现点击外部关闭菜单 - Hover触发:绑定
(mouseenter)/(mouseleave)事件控制显隐,处理多级菜单的hover冒泡问题,避免子菜单误关闭
二、多方向展开实现
通过动态绑定CSS类控制菜单定位,支持上下左右四个方向:
- 定义
@Input() direction参数,默认值为bottom - 针对不同方向编写定位样式,也可拓展自动计算视口空间的逻辑(可选优化)
三、多级菜单结构处理
基于传入的Pages数组(@Input类型)递归渲染菜单:
- 每个菜单项若包含
children属性,则渲染子菜单 - 子菜单默认方向设为
right,支持自定义传入方向 - 使用
ng-template实现递归渲染,简化层级结构
完整核心代码示例
组件类(menu.component.ts)
import { Component, Input, HostListener } from '@angular/core'; export interface MenuItem { label: string; route?: string; children?: MenuItem[]; direction?: 'top' | 'bottom' | 'left' | 'right'; isSubMenuOpen?: boolean; } @Component({ selector: 'app-custom-menu', templateUrl: './custom-menu.component.html', styleUrls: ['./custom-menu.component.css'] }) export class CustomMenuComponent { @Input() pages: MenuItem[] = []; @Input() triggerType: 'click' | 'hover' = 'click'; @Input() direction: 'top' | 'bottom' | 'left' | 'right' = 'bottom'; isMenuOpen = false; @HostListener('document:click', ['$event']) handleDocumentClick(event: MouseEvent) { const target = event.target as HTMLElement; if (!target.closest('.custom-menu-trigger') && !target.closest('.custom-menu')) { this.closeAllMenus(); } } toggleMenu() { if (this.triggerType === 'click') { this.isMenuOpen = !this.isMenuOpen; } } openSubMenu(item: MenuItem) { if (this.triggerType === 'hover') { this.closeAllSubMenus(); item.isSubMenuOpen = true; } } closeSubMenu(item: MenuItem) { if (this.triggerType === 'hover') { item.isSubMenuOpen = false; } } handleItemClick(item: MenuItem) { if (!item.children) { // 此处添加菜单项点击逻辑(如路由跳转) this.closeAllMenus(); } else { item.isSubMenuOpen = !item.isSubMenuOpen; } } private closeAllMenus() { this.isMenuOpen = false; this.closeAllSubMenus(); } private closeAllSubMenus() { const closeChildren = (items: MenuItem[]) => { items.forEach(item => { item.isSubMenuOpen = false; if (item.children) closeChildren(item.children); }); }; closeChildren(this.pages); } getSubMenuClass(direction?: string) { const dir = direction || 'right'; return `menu-${dir}`; } }
模板(custom-menu.component.html)
<button class="custom-menu-trigger" (click)="toggleMenu()" (mouseenter)="triggerType === 'hover' && (isMenuOpen = true)" (mouseleave)="triggerType === 'hover' && (isMenuOpen = false)"> 打开菜单 </button> <div *ngIf="isMenuOpen" class="custom-menu" [ngClass]="`menu-${direction}`"> <ng-template #menuItem let-items> <ul class="menu-list"> <li *ngFor="let item of items" class="menu-item"> <div class="item-content" (click)="handleItemClick(item)" (mouseenter)="openSubMenu(item)" (mouseleave)="closeSubMenu(item)"> <span>{{item.label}}</span> <span *ngIf="item.children" class="menu-arrow">›</span> </div> <div *ngIf="item.children && item.isSubMenuOpen" class="custom-submenu" [ngClass]="getSubMenuClass(item.direction)"> <ng-container *ngTemplateOutlet="menuItem; context: { $implicit: item.children }"></ng-container> </div> </li> </ul> </ng-template> <ng-container *ngTemplateOutlet="menuItem; context: { $implicit: pages }"></ng-container> </div>
样式(custom-menu.component.css)
.custom-menu-trigger { padding: 8px 16px; border: none; border-radius: 4px; background: #f1f1f1; cursor: pointer; } .custom-menu, .custom-submenu { position: absolute; background: white; border: 1px solid #e0e0e0; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); min-width: 180px; z-index: 1000; } .custom-submenu { z-index: 1001; } .menu-top { bottom: 100%; left: 0; margin-bottom: 8px; } .menu-bottom { top: 100%; left: 0; margin-top: 8px; } .menu-left { right: 100%; top: 0; margin-right: 8px; } .menu-right { left: 100%; top: 0; margin-left: 8px; } .menu-list { list-style: none; padding: 0; margin: 0; } .menu-item { position: relative; } .item-content { padding: 8px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; } .item-content:hover { background: #f5f5f5; } .menu-arrow { font-size: 12px; color: #999; }
内容的提问来源于stack exchange,提问作者Andre
相关产品推荐
相关产品推荐

