ng-带有材质菜单的模板

问题描述 投票:0回答:2

我正在开发一个具有可配置菜单结构的样板项目。其中的菜单绑定到递归数据类型。我想通过 ng-container 和 ng-template 来解决这个问题,因为在我看来,这是递归执行某些步骤的唯一方法。

我已经使用材质开发了几个组件,所以我也想坚持使用材质组件,而不是使用其他菜单组件。

我遇到的问题是,如果 mat-menu 项是在 ng-template 中定义的,则菜单无法正确呈现。 [https://i.imgur.com/WQed1yb.gif]

菜单项的菜单触发似乎没有按应有的方式呈现。它缺少子菜单图标,将鼠标悬停在其上不会显示子菜单。相反,需要单击一下。此外,当子菜单打开时,父菜单会消失,这会使菜单导航变得复杂。

除了提到的问题之外,菜单本身似乎确实有效。

我根据这里的示例尝试了延迟加载:https://www.angularjswiki.com/material/menu/但是 没有解决问题。

我的HTML模板如下:

<button mat-button [matMenuTriggerFor]="itemMenu">
  {{ startMenuItem.description }}
</button>
<mat-menu #itemMenu="matMenu">
  <ng-container *ngFor="let childItem of startMenuItem.children">
    <ng-container
      *ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
    >
    </ng-container>
  </ng-container>
</mat-menu>

<ng-template #recursiveMenuTmpl let-parentItem>
  <ng-container *ngIf="parentItem.children">
    <ng-container
      *ngTemplateOutlet="menuItem; context: { $implicit: parentItem }"
    ></ng-container>
  </ng-container>
  <ng-container *ngIf="!parentItem.children">
    <ng-container
      *ngTemplateOutlet="singleItem; context: { $implicit: parentItem }"
    ></ng-container>
  </ng-container>
</ng-template>

<ng-template #menuItem let-rootItem>
  <button [matMenuTriggerFor]="itemMenu" mat-menu-item>
    {{ rootItem.description }}
  </button>
  <mat-menu #itemMenu="matMenu">
    <ng-container *ngFor="let childItem of rootItem.children">
      <ng-container
        *ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem }"
      >
      </ng-container>
    </ng-container>
  </mat-menu>
</ng-template>

<ng-template #singleItem let-item>
  <button *ngIf="item.route" mat-menu-item [routerLink]="item.uri">
    {{ item.description }}
  </button>
  <button *ngIf="!item.route" mat-menu-item (click)="onOpenUrl(item.uri)">
    {{ item.description }}
  </button>
</ng-template>

和我的组件

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-dynamic-menu',
  templateUrl: './dynamic-menu.component.html',
  styleUrls: ['./dynamic-menu.component.css'],
})
export class DynamicMenuComponent implements OnInit {
  public startMenuItem: MenuItem = {
    description: 'Menu',
    uri: '',
    route: false,
    children: [
      {
        description: 'Welcome',
        uri: '',
        route: false,
        children: [],
      },
      {
        description: 'Other',
        uri: '',
        route: false,
        children: [],
      },
      {
        description: 'Sign on/off',
        uri: '',
        route: false,
        children: [
          {
            description: 'Login',
            uri: '',
            route: false,
            children: [],
          },
          {
            description: 'Logout',
            uri: '',
            route: false,
            children: [],
          },
        ],
      },
    ],
  };

  constructor() {}

  public onOpenUrl(url: string) {
    document.location.href = url;
  }

  ngOnInit(): void {
  }
}

html angular-material menu
2个回答
4
投票

在做了一些额外的研究之后,我发现我的代码中实际上存在一个错误。我的模板中检查菜单项是否有子项的

ngIf
检查子数组是否存在,而不检查数组是否具有实际内容。我解决了这个问题,但不幸的是它没有解决我原来的问题。

对材质菜单行为的进一步分析表明,父子关系的行为并不符合预期。这最终引导我发现了 Angular 已经提出的问题 (https://github.com/angular/angular/issues/14842)。我非常怀疑这个问题导致菜单行为异常。了解这一缺点后,我重新设计了模板,仅使用一个

ng-template

材质菜单的问题是缺少子项内容(至少在我的例子中,因为子项位于 ng-template 中)。但是,如果来自

mat-menu
的引用与模板一起传递,则模板中的自定义指令(或者至少这是我修复它的方式)可以执行内容子级并将结果设置在父级
mat-menu
中。在父级
_allItems
中设置
mat-menu
和内容子级并调用
_updateDirectDescendants
就足够了。

不幸的是,模板中指令的依赖注入也存在问题(请参阅:https://github.com/angular/angular/issues/2907),这会阻止在

menu-items
menu-trigger
中设置父关系。如果所有
parentMenu
中的
menuItems
设置为其相应的父菜单(通过模板上下文传递引用),则
menuItems
的问题也能得到解决。 在菜单触发器上需要设置
_parentMaterialMenu
。最后,对于触发子菜单的
matMenuItems
,应将
triggerSubMenu
设置为 true。


所以总结一下:

  1. 使用一个模板或至少确保菜单和后代菜单项位于一个模板中。
  2. 获取子项
    menuItems
    (例如通过自定义指令)并将其设置在菜单中的
    _allItems
    中。然后拨打菜单上的
    _updateDirectDescendants
  3. 设置所有
    menuItems
    ,将
    parentMenu
    设置为实际的父菜单。
  4. menuTrigger
    上,
    _parentMaterialMenu
    需要设置为实际的父菜单。
  5. 触发子菜单的
    menuItems
    应将
    _triggerSubMenu
    设置为 true。

就是这样,菜单应该可以正常工作。请注意,此修复不仅仅依赖于 Angular API。它适用于 Angular 材质版本 12,但也可能适用于其他版本。

以下代码是一个工作示例:

动态菜单组件html模板

<button mat-button [matMenuTriggerFor]="itemMenu">{{ startMenuItem.description }}
    </button>
    <mat-menu #itemMenu="matMenu" #parentMenuRef>
        <ng-container *ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: startMenuItem, parentMenuRef: parentMenuRef }"></ng-container>
    </mat-menu>
    <ng-template #recursiveMenuTmpl let-rootItem let-parentMenuRef="parentMenuRef">
      <ng-container appMenupatch [parentMenu]=parentMenuRef>
        <ng-container *ngFor="let childItem of rootItem.children" >
          <ng-container *ngIf="hasSubItems(childItem)">
            <button #triggerButton="matMenuItem" #trigger="matMenuTrigger" [matMenuTriggerFor]="patch(itemMenu, triggerButton, trigger, parentMenuRef)" mat-menu-item>
              {{ childItem.description }}
            </button>
            <mat-menu #itemMenu="matMenu" #parentMenuRefNew>
              <ng-container *ngTemplateOutlet="recursiveMenuTmpl; context: { $implicit: childItem, parentMenuRef: parentMenuRefNew }"></ng-container>
            </mat-menu>
          </ng-container>
          <ng-container *ngIf="!hasSubItems(childItem)">
            <button *ngIf="childItem.route" mat-menu-item [routerLink]="childItem.uri">
              {{ childItem.description }}
            </button>
            <button *ngIf="!childItem.route" mat-menu-item (click)="onOpenUrl(childItem.uri)">
              {{ childItem.description }}
            </button>
          </ng-container>
        </ng-container>
      </ng-container>
    </ng-template>

动态菜单组件ts

import { Component, OnInit } from '@angular/core';
import { MatMenu, MatMenuItem, MatMenuPanel, _MatMenuBase } from '@angular/material/menu';

export interface MenuItem {
  description: string,
  uri: string,
  route: boolean,
  children: MenuItem[]
}

@Component({
  selector: 'app-dynamic-menu',
  templateUrl: './dynamic-menu.component.html',
  styleUrls: ['./dynamic-menu.component.css'],
})

export class DynamicMenuComponent implements OnInit {
  public startMenuItem: MenuItem = {
    description: 'Menu',
    uri: '',
    route: false,
    children: []
   };

  constructor() {}

  public onOpenUrl(url: string) {
    document.location.href = url;
  }

  public hasSubItems(item: MenuItem):boolean{
    return (Array.isArray(item.children) && item.children.length>0);
  }

  public patch(item: MatMenu, triggerbutton: MatMenuItem, trigger: any, parentMenu: MatMenuPanel):MatMenu
  {
    if (parentMenu)
    {
        triggerbutton._triggersSubmenu=true;
        trigger._parentMaterialMenu=parentMenu;
    }
    return item;
  }

  ngOnInit(): void {
    let menuItem = {
      description: 'Canine',
      route: true,
      children: 
    [
      {
        description: 'Dog',
        route: false
      },
      {
        description: 'Wolve',
        route: false
      }
    ]
    } as MenuItem;
    this.startMenuItem.children.push(menuItem);
    menuItem = {
      description: 'Rodent',
      route: true,
      children:[
        {
          description: 'Rabbit',
          route: false
        },
        {
          description: 'Beaver',
          route: false
        },
        {
          description: 'Mouse',
          route: false
        }
      ]
    } as MenuItem;
    this.startMenuItem.children.push(menuItem);
    menuItem = {
      description: 'Bird of prey',
      route: true,
      children:
      [
        {
          description: 'Eagle',
          route: false
        },
        {
          description: 'Falcon',
          route: false
        },
        {
          description: 'Harrier',
          route: false
        }
      ]
    } as MenuItem;
    this.startMenuItem.children.push(menuItem);
  }
}

菜单补丁指令

import { Directive, Input, ContentChildren, QueryList } from '@angular/core';
import { MatMenuItem } from '@angular/material/menu';

@Directive({
  selector: '[appMenupatch]'
})
export class MenupatchDirective {
  @ContentChildren(MatMenuItem, {descendants: true}) _allItems: QueryList<MatMenuItem>;
  @Input() parentMenu: any;
  constructor() {
  }

  ngAfterViewChecked(){
    this._allItems.forEach(item=>{
      item._parentMenu=this.parentMenu;
    })
    this.parentMenu._allItems=this._allItems;
    this.parentMenu._updateDirectDescendants();
  }

}

模型声明应包含:

 - DynamicMenuComponent
 - MenupatchDirective

进口应具有材料模块组件:

- MatMenuModule
- MatButtonModule

0
投票

也尝试使用模板来解决这个问题,经过几个小时的尝试和网络搜索,我设法通过使用递归组件使其工作。我编辑了别人的旧 Angular 4 解决方案。分享一个在 Angular 13 上测试的工作示例。

https://stackblitz.com/edit/dynamic-nested-hover-mat-menu-nosubitem-support-ifcyo8

© www.soinside.com 2019 - 2024. All rights reserved.