将 ng-alain 中的菜单替换成 ng-zorro-antd 的 nz-menu

首先,不得不说 @delon 是一个很不错的业务组件,它是 ng-alain 默认使用的业务组件!

但是由于 delon 的菜单组件 sidebar-nav 的样式是基于 css 控制的,所以当菜单折叠后,展示出来的子菜单,在某些情况操作下无法正常关闭,是需要通过监听鼠标的点击事件来关闭的,因此我才决定使用 nz-menu去替换它,并且同时不破坏其它功能(如:路由守卫、在 pad 下自动收缩菜单等)


一. 新建一个组件例如 sidebar-menu (目前是我将该组件建在 shared 目录下)

$ cd src/app/shared/
$ mkdir components
$ cd components
$ ng g ng-zorro-antd:menu-inline-collapsed -p app --styleext='less' --name=sidebar-menu

二. 修改 sidebar-menu.component.html

<ul nz-menu [nzMode]="'inline'" nzTheme='light' [nzInlineCollapsed]="settings.layout.collapsed">
  <ng-container *ngFor="let group of list">
    <ng-template [ngIf]="group._hidden !== true">
      <!-- 菜单分组 -->
      <li nz-menu-group [hidden]="settings.layout.collapsed">
        <span title>{{ group.text }}</span>
      </li>

      <!-- 写法一:写死
        由于 angular 的问题 ngTemplateOutlet 生成的内容现在 viewchildren 获取不到
        因此目前先写死三层(一般超过三层就不应该放在菜单里面)
        https://github.com/angular/angular/issues/20810
      -->
      <!-- 第一层 -->
      <ng-container *ngFor="let child1 of group.children">
        <ng-container *ngIf="child1._hidden !== true">
          <ng-container *ngIf="child1._type !== 3">
            <li nz-menu-item [nzSelected]="child1._open" (click)="onSelect(child1)">
              <span title>
                <i class="{{ child1.icon }}"></i>
                <span>{{ child1.text }}</span>
              </span>
            </li>
          </ng-container>
          <ng-container *ngIf="child1._type === 3">
            <li nz-submenu [nzOpen]="child1._open">
              <span title>
                <i class="{{ child1.icon }}"></i>
                <span>{{ child1.text }}</span>
              </span>
              <ul>
                <!-- 第二层 -->
                <ng-container *ngFor="let child2 of child1.children">
                  <ng-container *ngIf="child2._hidden !== true">
                    <ng-container *ngIf="child2._type !== 3">
                      <li nz-menu-item [nzSelected]="child2._open" (click)="onSelect(child2)">{{ child2.text }}</li>
                    </ng-container>
                    <ng-container *ngIf="child2._type === 3">
                      <li nz-submenu [nzOpen]="child2._open">
                        <span title>
                          <i class="{{ child2.icon }}"></i>
                          <span>{{ child2.text }}</span>
                        </span>
                        <ul>
                          <!-- 第三层 -->
                          <ng-container *ngFor="let child3 of child2.children">
                            <li nz-menu-item *ngIf="child3._hidden !== true" [nzSelected]="child3._open" (click)="onSelect(child3)">{{ child3.text }}</li>
                          </ng-container>
                        </ul>
                      </li>
                    </ng-container>
                  </ng-container>
                </ng-container>
              </ul>
            </li>
          </ng-container>
        </ng-container>
      </ng-container>

      <!-- 写法二 -->
      <!-- 循环嵌套层 (需等待bug修复 https://github.com/NG-ZORRO/ng-zorro-antd/issues/1326 ) -->
      <!-- <ng-container *ngFor="let child of group.children">
        <ng-container *ngTemplateOutlet="sidebarMenuTemplate; context: { $implicit: child }"></ng-container>
      </ng-container> -->
    </ng-template>
  </ng-container>

  <!-- 写法二 -->
  <!-- 菜单嵌套模板 -->
  <!-- <ng-template #sidebarMenuTemplate let-navmenu>
    <ng-container *ngIf="navmenu._hidden !== true">
      <ng-container *ngIf="navmenu._type !== 3">
        <li nz-menu-item [nzSelected]="navmenu._open" (click)="onSelect(navmenu)">
          <ng-container *ngIf="navmenu._depth <= 1">
            <span title>
              <i class="{{ navmenu.icon }}"></i>
              <span>{{ navmenu.text }}</span>
            </span>
          </ng-container>
          <ng-container *ngIf="navmenu._depth > 1">
            {{ navmenu.text }}
          </ng-container>
        </li>
      </ng-container>

      <ng-container *ngIf="navmenu._type === 3">
        <li nz-submenu [nzOpen]="navmenu._open">
          <span title>
            <i class="{{ navmenu.icon }}"></i>
            <span>{{ navmenu.text }}</span>
          </span>
          <ul>
            <ng-container *ngFor="let c of navmenu.children">
              <ng-container *ngTemplateOutlet="sidebarMenuTemplate; context: { $implicit: c }"></ng-container>
            </ng-container>
          </ul>
        </li>
      </ng-container>
    </ng-container>
  </ng-template> -->
</ul>

写法一是写死的,只有三层
写法二在样式上存在没有缩进的问题,你可以根据菜单的深度( menu._depth ) 手动在 sidebarMenuTemplate 模板中加入缩进样式

三. 修改 sidebar-menu.component.ts

import {
  Component,
  OnInit,
  OnDestroy,
  ChangeDetectorRef,
  Input,
  Output,
  EventEmitter,
} from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { ReuseTabService } from '@delon/abc';
import { MenuService, SettingsService, Menu } from '@delon/theme';
import { Nav } from '@delon/abc/src/sidebar-nav/interface';

@Component({
  selector: 'app-sidebar-menu',
  templateUrl: './sidebar-menu.component.html',
})
export class SidebarMenuComponent implements OnInit, OnDestroy {
  list: Nav[] = [];
  private menuChange$: Subscription;
  private reuseChange$: Subscription;

  @Input() autoCloseUnderPad = true;

  @Output() select = new EventEmitter<Menu>();

  constructor(
    private menuSrv: MenuService,
    private reuseSrv: ReuseTabService,
    public settings: SettingsService,
    private router: Router,
    private cd: ChangeDetectorRef,
  ) {}

  ngOnInit() {
    this.menuSrv.openedByUrl(this.router.url);
    this.menuChange$ = <any>this.menuSrv.change.subscribe(res => {
      this.list = res;
      this.cd.detectChanges();
    });
    this.reuseChange$ = <any>this.reuseSrv.change.subscribe(res => {
      this.updateOpen();
      this.cd.detectChanges();
    });
    this.installUnderPad();
  }

  updateOpen() {
    const currentLink = this.router.url;
    let imenu: Nav;
    this.menuSrv.visit((i, p) => {
      if (i.link === currentLink) {
        imenu = i;
      } else {
        i._open = false;
      }
    });
    while (imenu) {
      imenu._open = true;
      imenu = imenu.__parent;
    }
    this.cd.markForCheck();
  }

  onSelect(item: Nav) {
    this.select.emit(item);
    if (item._type === 1) {
      this.router.navigateByUrl(item.link);
    } else if (item._type === 2) {
      // ......
    }
  }

  ngOnDestroy(): void {
    this.menuChange$.unsubscribe();
    this.reuseChange$.unsubscribe();
    if (this.route$) {
      this.route$.unsubscribe();
    }
  }

  // region: Under pad

  private route$: Subscription;
  private installUnderPad() {
    if (!this.autoCloseUnderPad) return;
    this.route$ = <any>this.router.events.pipe(filter(e => e instanceof NavigationEnd)).subscribe(s => this.underPad());
    this.underPad();
  }

  private underPad() {
    if (window.innerWidth < 992 && !this.settings.layout.collapsed) {
      this.settings.setLayout('collapsed', true);
    }
  }

  // endregion
}

四. 组件替换

将 layout/default/sidebar/sidebar.component.html 中的<sidebar-nav class="d-block py-lg"></sidebar-nav>替换成<app-sidebar-menu class="d-block py-lg"></app-sidebar-menu>

五. 样式调整

  1. 因为 ng-zorro-antd 的 nz-menu 折叠后的默认宽度是 80px,而 ng-alain 框架预留的宽度是64px,所以需要在 theme.less 中添加 @menu-collapsed-width: 64px; 修改 nz-menu 的宽度
  2. 菜单收缩后会显示半截 title 的问题,因为 ng-zorro-antd 有一套自己的图标规范,因此按照它的规范使用<i class="anticon anticon-${type}"></i>,不建议在代码中对<i class="{{ child1.icon }}"></i> 添加 margin-right
  3. 目前因为 angular 的问题,不限层级的写法还存在问题,因此默认写死最多展示三级,具体情况代码中我写的已经很清楚了

六. 与 sidebar-nav 的API 不兼容的问题

  1. 不支持 tooltip 和 badge,因为 ng-zorro-antd 并没有此功能,所以我没有花额外的时间去整合它,若你需要它,你只需稍微改动 sidebar-menu.component.html 即可
  2. 关于 externalLink 和 target ,我并不清楚应用场景,所以也未作深究,你可以修改 sidebar-menu.component.ts 中的 onSelect 方法去实现它
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容