6

Please tell me how I can solve the following problem: I need to implement a dynamically created menu with different nesting levels depending on the data model object. At the moment, using recursion, we managed to create the menu as such, however, there is a problem of assigning the attribute [matMenuTriggerFor] for, directly, the submenu. The problem is that all subsequent submenus in fact refer to the very first, so when you hover over any of the submenus, it causes a "flip" to the original one (example on image: menu, which includes elements: Device, Extension, Queue, Queue member (with submenu elements)). Thus, for a fraction of seconds, I see the other submenu frame (example on image: submenu Grouped list), after which the very first becomes active. Of course, maybe I didn’t do everything right, so I’m turning here. Help me please. Thank you all.

Dynamically nested material menu

imenu-item.ts

export interface IMenuItem {
  name: string | string[];
  link: string;
  subItems: IMenuItem[];
}

dynamic-menu.service.ts

import {Inject, Injectable} from '@angular/core';
import {APP_CONFIG_ROUTES} from '../../../config/routes/app.config.routes';
import {IAppConfigRoutes} from '../../../config/routes/iapp.config.routes';
import {IMenuItem} from './imenu-item';
import {_} from '@biesbjerg/ngx-translate-extract/dist/utils/utils';

@Injectable({
  providedIn: 'root'
})
export class DynamicMenuService {
  private readonly appConfig: any;

  constructor(@Inject(APP_CONFIG_ROUTES) appConfig: IAppConfigRoutes) {
    this.appConfig = appConfig;
  }

  getMenuItems(): IMenuItem[] {
    return [
      {
        name: _('labels.device'),
        link: '/' + this.appConfig.routes.device,
        subItems: null
      },
      {
        name: _('labels.extension'),
        link: '/' + this.appConfig.routes.extension,
        subItems: null
      },
      {
        name: _('labels.queue'),
        link: '/' + this.appConfig.routes.queue,
        subItems: null
      },
      {
        name: _('labels.queueMember'),
        link: null,
        subItems: [{
          name: _('labels.fullList'),
          link: '/' + this.appConfig.routes.queueMember.all,
          subItems: null
        }, {
          name: _('labels.groupedList'),
          link: '/' + this.appConfig.routes.queueMember.grouped,
          subItems: [{
            name: 'subName',
            link: 'subLink',
            subItems: [{
              name: 'subSubName1',
              link: 'subSubLink1',
              subItems: null
            }, {
              name: 'subSubName2',
              link: 'subSubLink2',
              subItems: null
            }]
          }]
        }]
      }
    ];
  }
}

dynamic-menu.component.ts

import {Component, Input, OnInit} from '@angular/core';
import {IMenuItem} from './imenu-item';

@Component({
  selector: 'app-dynamic-menu',
  templateUrl: './dynamic-menu.component.html',
  styleUrls: ['./dynamic-menu.component.scss']
})
export class DynamicMenuComponent implements OnInit {
  dynamicMenuItemsData: IMenuItem[];

  constructor(private dynamicMenuService: DynamicMenuService) {
  }

  ngOnInit() {
   this.dynamicMenuItemsData = this.dynamicMenuService.getMenuItems();
  }
}

dynamic-menu.component.html

<div>
  <ng-container [ngTemplateOutlet]="recursiveListMenuItems"
                [ngTemplateOutletContext]="{$implicit: dynamicMenuItemsData}">
  </ng-container>
</div>

<ng-template #recursiveListMenuItems let-listMenuItems>
  <div *ngFor="let menuItem of listMenuItems">
    <ng-container [ngTemplateOutlet]="menuItem.subItems != null ? subMenuItem : simpleMenuItem"
                  [ngTemplateOutletContext]="{$implicit: menuItem}">
    </ng-container>
  </div>
</ng-template>

<ng-template #simpleMenuItem let-menuItemArg>
  <a class="mat-button"
     mat-menu-item
     routerLink="{{menuItemArg.link}}">
    <span>{{menuItemArg.name | translate}}</span>
  </a>
</ng-template>

<ng-template #subMenuItem let-menuItemArg>
  <a class="mat-button"
     mat-menu-item
     routerLink="{{menuItemArg.link}}"
     [matMenuTriggerFor]="subItemsMenu">
    <span>{{menuItemArg.name | translate}}</span>
    <mat-menu #subItemsMenu="matMenu"
              [overlapTrigger]="false">
      <ng-container [ngTemplateOutlet]="recursiveListMenuItems"
                    [ngTemplateOutletContext]="{$implicit: menuItemArg.subItems}">
      </ng-container>
    </mat-menu>
  </a>
</ng-template>
titaniche
  • 197
  • 1
  • 1
  • 10

2 Answers2

6

As a result, it turned out, relying on several similar problems with others. The examples from HERE (dynamic nested menu example) and from HERE (the problem with mat-menu hides immediately on opening) helped to figure it out (in the last example it was enough just to update zone.js by npm)

titaniche
  • 197
  • 1
  • 1
  • 10
1

Sorry for the late answer, but maybe you can still find it helpful.
I wrote a little library called ng-action-outlet that is doing that quite neatly in my opinion.


It looks like this:

group: ActionGroup;

constructor(private actionOutlet: ActionOutletFactory) {
    this.group = this.actionOutlet.createGroup();

    this.group.createButton().setIcon('home').fire$.subscribe(this.callback);
    this.group.createButton().setIcon('settings').fire$.subscribe(this.callback);
}
<ng-container *actionOutlet="group"></ng-container>

DEMO: https://stackblitz.com/edit/ng-action-outlet-demo?file=src/app/app.component.ts

Clem
  • 11,334
  • 8
  • 34
  • 48
  • 1
    Thanks for the answer. Now the question itself is not particularly relevant, but in the future I will try to focus on your option) – titaniche Jun 14 '19 at 15:22