How to open mat-menu programmatically?

Viewed 52054

Im trying to trigger opening a menu when clicking mat-nav-list item.

HTML

<mat-nav-list>
    <mat-list-item (click)="onOpenMenu(menu)" *ngFor="let i of data">
        <div mat-line>
            {{ i.name }}
        </div>
        <p mat-line>
            {{ i.email }}
        </p>
        <button mat-icon-button [matMenuTriggerFor]="menu">
            <mat-icon>more_vert</mat-icon>
        </button>
    </mat-list-item>
    <mat-menu #menu="matMenu">
        <button mat-menu-item>View profile</button>
        <button mat-menu-item>Add contact</button>
    </mat-menu>
</mat-nav-list>

TS

onOpenMenu(menu: any): void {
   // menu doesn't have any openMenu() function 
   // which is of course not a trigger object but a menu itself.
   console.log(menu);
}

I have been trying to look at this issue on github which is closer to my situation. but in my case i have a dynamic list of items which i wanted to open a menu every click.

DEMO

3 Answers

You need to get hold of MatMenuTrigger instance from [matMenuTriggerFor] element

#menuTrigger="matMenuTrigger"  [matMenuTriggerFor]="menu"

where

  • #menuTrigger is the template reference variable

  • matMenuTrigger is exportAs property of MatMenuTrigger metadata

and then simply call

(click)="menuTrigger.openMenu()"

Stackblitz example

Read more about template reference variable here

You can also control the <mat-menu> directly in the code. Here is an example:

import { Component, ViewChild } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';

...
export class LanguageComponent {
  @ViewChild('languageMenuTrigger') languageMenuTrigger: MatMenuTrigger;
  
  ...

  openMenu() {
    this.languageMenuTrigger.openMenu();
  }
  
  closeMenu() {
    this.languageMenuTrigger.closeMenu();
  }
}
<button
  mat-button
  [matMenuTriggerFor]="languageMenu"
  #languageMenuTrigger="matMenuTrigger">
  Language
  <mat-icon>expand_more</mat-icon>
</button>
<mat-menu #languageMenu="matMenu">
  <button
    mat-menu-item>
    English
  </button>
  <button
    mat-menu-item>
    Français
  </button>
</mat-menu>

If you want to completely control it programmatically, without having an element on your website that also controls the menu, you can declare an element containing [matMenuTriggerFor], but hiding it via CSS.

Caveat: You will have to manually position the menu. You can do it roughly like that:

Template:

<div #menuTrigger [matMenuTriggerFor]="menu" class="menu-trigger">I will be hidden</div>
<mat-menu #menu class="your-menu-class">
...
</mat-menu>

CSS:

.menu-trigger {
  display: none;
}

Component:

class YourComponent {
  @ViewChild(MatMenuTrigger) menuTrigger: MatMenuTrigger | undefined;

  constructor(private readonly viewRef: ViewContainerRef) { }

  someMethod() {
    this.menuTrigger?.menuOpened.pipe(take(1)).subscribe(() => {
      const menu = document.getElementsByClassName('your-menu-class')[0] as HTMLElement;
      const position: DOMRect = this.viewRef.element.nativeElement.getBoundingClientRect();

      menu.style.position = 'absolute';
      menu.style.top = `${position.top}px`;
      menu.style.left = `${position.width}px`;
    });

    this.menuTrigger?.openMenu();
  }
}
Related