Search code examples
angularangular-materialangular-material-table

Context Menu per row mat-table with multiple submenus. How I can add custom menu with multiple submenu?


I need a Context Menu per row mat-table with multiple submenus. How I can add custom menu with multiple submenu? I need to use Angular 11 and Angular Material 11.

enter image description here

Could you help me with this code?

This is my current code and I can only use the main submenu

HTML file

  <td
    mat-cell
    *matCellDef="let row"
    class="custom-table-cell"
    [style.width]="columnsConfiguration[column].width"
    (contextmenu)="
      currentTableOptions.showMenuActions &&
        currentRowMenuActions.length &&
        onContextMenu(row, $event)
    ">
...
<!-- START Menu per row -->
<span
  #contextMenuTrigger
  style="visibility: hidden; position: fixed"
  [style.left]="contextMenuPosition.x"
  [style.top]="contextMenuPosition.y"
  [matMenuTriggerFor]="contextMenu"
  (menuOpened)="hasContextMenu = true"
  (menuClosed)="hasContextMenu = false"></span>

<mat-menu
  #contextMenu="matMenu"
  backdropClass="actions-selector-menu-backdrop"
  class="actions-selector-menu">
  <ng-template
    matMenuContent
    let-row="row">
    <button
      mat-menu-item
      *ngFor="let rowMenuAction of currentRowMenuActions"
      (click)="onRowMenuAction(row, rowMenuAction, $event)"
      [disabled]="
        rowMenuAction.isDisabled ? rowMenuAction.isDisabled(row) : false
      ">
      {{ rowMenuAction.translateKey | translate }}
    </button>
  </ng-template>
</mat-menu>
<!-- END Menu per row -->

TS file. I create a contextmenu content

  // Menu per row
  @ViewChild('contextMenuTrigger', { read: MatMenuTrigger })
  contextMenu!: MatMenuTrigger;
  contextMenuPosition = { x: '0px', y: '0px' };
  onContextMenu(row: T, event: MouseEvent) {
    event.preventDefault();

    this.contextMenuPosition.x = event.clientX + 'px';
    this.contextMenuPosition.y = event.clientY + 'px';

    this.contextMenu.menuData = { row };
    this.contextMenu.menu.focusFirstItem('mouse');
    this.contextMenu.openMenu();
  }

  // Actions per menu row
  @Input() set rowMenuActions(value: RowMenuAction<T>[]) {
    this.currentRowMenuActions = value;
  }
  currentRowMenuActions: RowMenuAction<T>[] = [];

  @Output() rowMenuAction = new EventEmitter<RowMenuActionEvent<T>>();
  onRowMenuAction(row: T, action: RowMenuAction<T>, event: Event) {
    this.rowMenuAction.emit({ row, action, event });
  }

Thanks!


Solution

  • Solution is the next:

    context-menu.component.html

    <mat-menu
      #menu="matMenu"
      backdropClass="actions-selector-menu-backdrop"
      class="actions-selector-menu"
      [overlapTrigger]="false">
      <ng-template
        matMenuContent
        let-row="row">
        <ng-container *ngFor="let rowMenuAction of currentRowMenuActions">
          <!-- Handle branch node menu items -->
          <ng-container
            *ngIf="rowMenuAction.children && rowMenuAction.children.length > 0">
            <button
              mat-menu-item
              color="primary"
              [matMenuTriggerFor]="submenu.menu"
              [matMenuTriggerData]="{ row: row }">
              {{ rowMenuAction.translateKey | translate }}
            </button>
    
            <talan-context-menu
              #submenu
              [rowMenuActions]="rowMenuAction.children"
              (rowMenuAction)="onRowMenuAction($event)"></talan-context-menu>
          </ng-container>
    
          <!-- Handle leaf node menu items -->
          <button
            *ngIf="!rowMenuAction.children || rowMenuAction.children.length === 0"
            mat-menu-item
            (click)="
              onRowMenuAction({ row: row, action: rowMenuAction, event: $event })
            "
            [disabled]="
              rowMenuAction.isDisabled ? rowMenuAction.isDisabled(row) : false
            ">
            {{ rowMenuAction.translateKey | translate }}
          </button>
        </ng-container>
      </ng-template>
    </mat-menu>
    

    context-menu.component.ts

    import {
      Component,
      EventEmitter,
      Input,
      Output,
      ViewChild,
    } from '@angular/core';
    import { MatMenu } from '@angular/material/menu';
    import { RowMenuAction, RowMenuActionEvent } from '../../models/models';
    
    @Component({
      selector: 'talan-context-menu',
      templateUrl: './context-menu.component.html',
      styleUrls: ['./context-menu.component.scss'],
    })
    export class ContextMenuComponent<T> {
      @ViewChild('menu', { static: true }) menu!: MatMenu;
    
      // Actions per menu row
      @Input() set rowMenuActions(value: RowMenuAction<T>[]) {
        this.currentRowMenuActions = value;
      }
      currentRowMenuActions: RowMenuAction<T>[] = [];
    
      @Output() rowMenuAction = new EventEmitter<RowMenuActionEvent<T>>();
      onRowMenuAction(event: RowMenuActionEvent<T>) {
        this.rowMenuAction.emit(event);
      }
    }
    

    table.component.html

          <td
            mat-cell
            *matCellDef="let row"
            (contextmenu)="
            onContextMenu(row, $event)">
    
    <span
      #contextMenuTrigger="matMenuTrigger"
      style="visibility: hidden; position: fixed"
      [style.left]="contextMenuPosition.x"
      [style.top]="contextMenuPosition.y"
      [matMenuTriggerFor]="contextMenu.menu"
      (menuOpened)="hasContextMenu = true"
      (menuClosed)="hasContextMenu = false"></span>
    
    <context-menu
      #contextMenu
      [rowMenuActions]="currentRowMenuActions"
      (rowMenuAction)="onRowMenuAction($event)"></context-menu>
    

    table.component.ts

      @ViewChild('contextMenuTrigger') contextMenuTrigger!: MatMenuTrigger;
      @ViewChild('contextMenu') contextMenu!: ContextMenuComponent<T>;
    
      contextMenuPosition = { x: '0px', y: '0px' };
      onContextMenu(row: T, event: MouseEvent) {
        event.preventDefault();
    
        this.contextMenuPosition.x = event.clientX + 'px';
        this.contextMenuPosition.y = event.clientY + 'px';
    
        this.contextMenuTrigger.menu.focusFirstItem('mouse');
        this.contextMenuTrigger.menuData = { row };
        this.contextMenuTrigger.openMenu();
      }