How to open and close Angular mat menu on hover

The first challenge is that mat-menu steals the focus from the button when the CDK overlay is generated due to the z-index of the overlay… to solve this you need to set the z-index in a style for the button…

  • This will stop the recursive loop when you add a (mouseleave) to the button. style="z-index:1050"

Next you need to track the state of all enter and leave events for the levelone and levelTwo menu’s and store that state in two component variables.

enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;

Next create menu enter and menuLeave methods for both menu levels.. notice menuLeave(trigger) checks if level2 is accessed and does nothing if true.

Please Note: menu2Leave() has logic to allow navigation back to level one but close both if exit the other side… also removing button focus upon leave of levels.

menuenter() {
    this.isMatMenuOpen = true;
    if (this.isMatMenu2Open) {
      this.isMatMenu2Open = false;
    }
  }

  menuLeave(trigger, button) {
    setTimeout(() => {
      if (!this.isMatMenu2Open && !this.enteredButton) {
        this.isMatMenuOpen = false;
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.isMatMenuOpen = false;
      }
    }, 80)
  }

  menu2enter() {
    this.isMatMenu2Open = true;
  }

  menu2Leave(trigger1, trigger2, button) {
    setTimeout(() => {
      if (this.isMatMenu2Open) {
        trigger1.closeMenu();
        this.isMatMenuOpen = false;
        this.isMatMenu2Open = false;
        this.enteredButton = false;
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.isMatMenu2Open = false;
        trigger2.closeMenu();
      }
    }, 100)
  }

  buttonEnter(trigger) {
    setTimeout(() => {
      if(this.prevButtonTrigger && this.prevButtonTrigger != trigger){
        this.prevButtonTrigger.closeMenu();
        this.prevButtonTrigger = trigger;
        trigger.openMenu();
      }
      else if (!this.isMatMenuOpen) {
        this.enteredButton = true;
        this.prevButtonTrigger = trigger
        trigger.openMenu()
      }
      else {
        this.enteredButton = true;
        this.prevButtonTrigger = trigger
      }
    })
  }

  buttonLeave(trigger, button) {
    setTimeout(() => {
      if (this.enteredButton && !this.isMatMenuOpen) {
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } if (!this.isMatMenuOpen) {
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.enteredButton = false;
      }
    }, 100)
  }

HTML

below is how to wire it all up.

<ng-container *ngFor="let menuItem of modulesList">

    <ng-container *ngIf="!menuItem.children">
        <a class="nav-link">
            <span class="icon fa" [ngClass]="menuItem.icon"></span>
      <span class="text-holder">{{menuItem.label}}</span>
    </a>
  </ng-container>
  <ng-container *ngIf="menuItem.children.length > 0">
    <button #button mat-button [matMenuTriggerFor]="levelOne" #levelOneTrigger="matMenuTrigger" (mouseenter)="levelOneTrigger.openMenu()" (mouseleave)="buttonLeave(levelOneTrigger, button)" style="z-index:1050">
      <span class="icon fa" [ngClass]="menuItem.icon"></span>
      <span>{{menuItem.label}}
        <i class="fa fa-chevron-down"></i>
      </span>
    </button>

    <mat-menu #levelOne="matMenu" direction="down" yPosition="below">
      <span (mouseenter)="menuenter()" (mouseleave)="menuLeave(levelOneTrigger, button)">
      <ng-container *ngFor="let childL1 of menuItem.children">
        <li class="p-0" *ngIf="!childL1.children" mat-menu-item>
          <a class="nav-link">{{childL1.label}}
            <i *ngIf="childL1.icon" [ngClass]="childL1.icon"></i>
          </a>
        </li>
        <ng-container *ngIf="childL1.children && childL1.children.length > 0">
          <li mat-menu-item #levelTwoTrigger="matMenuTrigger" [matMenuTriggerFor]="levelTwo">
            <span class="icon fa" [ngClass]="childL1.icon"></span>
            <span>{{childL1.label}}</span>
          </li>

          <mat-menu #levelTwo="matMenu">
            <span (mouseenter)="menu2enter()" (mouseleave)="menu2Leave(levelOneTrigger,levelTwoTrigger, button)">
            <ng-container *ngFor="let childL2 of childL1.children">
              <li class="p-0" mat-menu-item>
                <a class="nav-link">{{childL2.label}}
                  <i *ngIf="childL2.icon" [ngClass]="childL2.icon"></i>
                </a>
              </li>
            </ng-container>
            </span>
          </mat-menu>
        </ng-container>
      </ng-container>
      </span>
    </mat-menu>
  </ng-container>

</ng-container>

Stackblitz

https://stackblitz.com/edit/mat-nested-menu-yclrmd?embed=1&file=app/nested-menu-example.html

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)