40

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
Shift 'n Tab
  • 8,808
  • 12
  • 73
  • 117

4 Answers4

70

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

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • @yurzui What happens if the menu button is shown only on hover of the menu item? Does the menu still work? – Kay Nov 19 '17 at 12:32
  • In my own implementation i get a openMenu is not defined.Its because the menu button is displayed with an ng-if on hover of the menu item – Kay Nov 19 '17 at 12:33
  • @KHAN Can you reproduce it in stackblitz? – yurzui Nov 19 '17 at 12:41
  • Ye i will, please check back later. – Kay Nov 19 '17 at 12:55
  • @yurzui Here https://stackblitz.com/edit/mat-list-nav-menu-item-t85phh?file=app/app.component.html – Kay Nov 19 '17 at 13:09
  • The menu button only displays on hover of the specific item. But now the menu dropdown does not work because it thinks its undefined due to the ngif and mouseleaving the item. – Kay Nov 19 '17 at 13:10
  • @KHAN Mouseleave event is firing when you open menu. That's because material creates overlay and you loose focus. Workaroung something like this https://stackblitz.com/edit/mat-list-nav-menu-item-dzfmvc?file=app/app.component.html but i would rather use css display none for this case – yurzui Nov 19 '17 at 13:29
  • I have a question aobut this. https://stackoverflow.com/questions/47370434/angular-5-on-mouse-enter-show-a-button-and-on-mouse-leave-hide-a-button?noredirect=1#comment81695749_47370434 If you want to add your display none solution instead that would be nice – Kay Nov 19 '17 at 13:32
  • 1
    how is this done from another component. Component A (a modal) triggers this popup in Component B. Any suggestions? – Gel Feb 12 '21 at 19:45
22

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>
Vincent LUC
  • 351
  • 3
  • 5
8

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();
  }
}
Dominik Ehrenberg
  • 1,533
  • 1
  • 15
  • 12
  • But... this is WITH an element "on your website"... although it is hidden it is still in the DOM and thus not completely programatically unfortunately. – Youp Bernoulli Jul 31 '23 at 15:04
0

HTML

<div (click)="openMenu()"> clike open menu</div>
<span [matMenuTriggerFor]="menu" class="menu-trigger"></span>
<mat-menu #menu="matMenu" [overlapTrigger]="false">
    <div mat-menu-item>a</div>
    <div mat-menu-item>b</div>
    <div mat-menu-item>c</div>
</mat-menu>

TS

openMenu() {
    const menu = document.querySelector('.menu-trigger')
    menu.dispatchEvent(new Event('click'))
}

Now you can put openMenu on anywhere.

zayn
  • 26
  • 3