How to prevent angular material mat-menu from closing?
Asked Answered
P

7

83

I'm creating a date time picker control in the angular material and having the below code to do that

<button mat-button [matMenuTriggerFor]="menu">
    <mat-icon>date_range</mat-icon>
    <span>Date Range</span>
</button>
<mat-menu #menu="matMenu">
    <div fxLayout="row">
        <div fxLayout="column">
            <button (click)="setInterval(15)" mat-menu-item>Last 15 minutes</button>
            <button (click)="setInterval(360)" mat-menu-item>Last 6 hours</button>
            <button (click)="setInterval(1440)" mat-menu-item>Last 24 hours</button>
            <button (click)="setInterval(2880)" mat-menu-item>Last 2 days</button>
            <button (click)="setInterval(10080)" mat-menu-item>Last 7 days</button>
            <button (click)="setInterval(-1)" [matMenuTriggerFor]="dateTimeMenu" mat-menu-item>Custom</button>
        </div>
        <mat-menu class="date-range-menu" #dateTimeMenu="matMenu">
            <div fxLayout="row">
                <div fxLayout="column">
                    <b>From</b>
                    <mat-calendar></mat-calendar>
                </div>
                <div fxLayout="column">
                    <b>To</b>
                    <mat-calendar></mat-calendar>
                </div>
            </div>
        </mat-menu>
    </div>
</mat-menu>

Currently when ever I click a button it is closing the menu. I know we can do $event.stoppropagation() on each mat-menu-item to prevent it from closing.

But I want to know is it possible to do that for mat-calendar

enter image description here

As you can see in the above image currently when i select a date it is closing the menu. Is it possible to prevent that?

Pendent answered 27/10, 2017 at 5:17 Comment(1)
Hi, were you successful in making such a date range selector? I need one and not really in the mood to reinvent the wheel.Vernita
B
177

You just add (click) = "$event.stopPropagation()" to the parent element of these calendars. Like below,

<mat-menu class="date-range-menu" #dateTimeMenu="matMenu">
    <div fxLayout="row">
        <div fxLayout="column" (click)="$event.stopPropagation();">
            <b>From</b>
            <mat-calendar></mat-calendar>
        </div>
        <div fxLayout="column" (click)="$event.stopPropagation();">
            <b>To</b>
            <mat-calendar></mat-calendar>
        </div>
    </div>
</mat-menu>

Stackblitz demo.

Bough answered 28/10, 2017 at 13:39 Comment(8)
doesn't seems to work well. Dates cannot be selected when you click on themComponent
it works perfectly for individual divs, but I wanted to do the same for entire mat-menu so that wherever user clicks inside mat-menu it shouldn't close and unfortunately $event.stoppropagation() doesn't work for entire menuJakie
Worked for me. I added a wrapper div inside mat-menu and added $event.stoppropagation() on it.Agata
It worked for me for checkbox in mat-menu, Thank you so muchMariande
One other problem with this solution is that events like blur from all elements inside are not fired when you click somewhere on the parent element. This caused me to mat-autocomplete not closing whenever I clicked elsewhere on the parent element, so I have to find another solutionCrackup
The solution was posted to address the specific issue the OP has posted. If you try it to solve your different problem then it's a "problem" in your approach not in the solution itself.Bough
I just used this solution to remove the mat-menu. :PMt
For what I wanted, it worked, I am using a mat-menu to display countries and want to add an input for search porpuses. ThanksItch
E
18

By giving a return to the previous solution, encapsulating the instruction in a method allows not to close the menu and continue executing instructions

IN HTML:

<mat-menu class="date-range-menu" #dateTimeMenu="matMenu">
    <div fxLayout="row">
        <div fxLayout="column" (click)="doSomething($event);">
            <b>From</b>
            <mat-calendar></mat-calendar>
        </div>
        <div fxLayout="column" (click)="doSomething($event)">
            <b>To</b>
            <mat-calendar></mat-calendar>
        </div>
    </div>
</mat-menu>

IN TS:

doSomething($event:any){
    $event.stopPropagation();
    //Another instructions
}
Evvy answered 26/2, 2019 at 12:37 Comment(0)
K
14

You have many options, I invite you try the following

    <mat-menu [hasBackdrop]="false">
     <div  (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
     ...
     </div>
    </mat-menu>

the [hasBackdrop]="false" if you want to prevent closing mat-menu when clicking anywhere outside the box, otherwise remove it

Kayceekaye answered 30/6, 2020 at 11:56 Comment(0)
W
6

if You want to stop closing mat-menu even on clicking on mat-menu-content i did hack as added $event.stopPropogation() on a anchor tag instead of mat-menu. so Menu dailog will not close even if clicked anywhere on the form.

Example:- 
    <mat-menu #nameAndDescriptioContextMenu="matMenu" [hasBackdrop]="false">
         <a (click)="$event.stopPropagation();$event.preventDefault();">
               <div>
                 Form Group Form
               </div> 
         </a> 
    </mat-menu>
Waggon answered 16/6, 2020 at 11:24 Comment(1)
Love it, brother. Good job!Interatomic
T
3

Unfortunately, none of the above answers worked for me. In cases when you need menu panel to be much wider than the content, there is no place you can put "$event.stopPropagation();" on, so if you click on mat-menu-panel it will close. Luckily, there is still a way to avoid this, by 'overriding' click event of MatMenu. Here is stackblitz example, thanks to my colleague: https://stackblitz.com/edit/mat-menu-disable-close

ngAfterViewInit() {
    // Inject our custom logic of menu close
    (this.searchMenu as any).closed = this.searchMenu.close = this.configureMenuClose(this.searchMenu.close);
  }

private configureMenuClose(old: MatMenu['close']): MatMenu['close'] {
    const upd = new EventEmitter();
    feed(upd.pipe(
      filter(event => {
        if (event === 'click') {
          // Ignore clicks inside the menu 
          return false;
        }
        return true;
      }),
    ), old);
    return upd;
  }
}
function feed<T>(from: Observable<T>, to: Subject<T>): Subscription {
  return from.subscribe(
    data => to.next(data),
    err => to.error(err),
    () => to.complete(),
  );
}

This way, it will close only if you click outside (that's an easy to remove) and if you use trigger. That is the behavior I wanted in my project and I hope it will be useful for someone.

Toweling answered 15/5, 2020 at 11:29 Comment(0)
U
3

Try this way, wrap all the mat menu items inside a div and stop propagating any click event.

<mat-menu #menu="matMenu" [hasBackdrop]="false">
      <div (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
        <button mat-menu-item>
          <mat-icon>dialpad</mat-icon>
          <span>Origin</span>
        </button>
        <button mat-menu-item disabled>
          <mat-icon>voicemail</mat-icon>
          <span>Check voice mail</span>
        </button>
        <button mat-menu-item>
          <mat-icon>notifications_off</mat-icon>
          <span>Disable alerts</span>
        </button>
      </div>
    </mat-menu>
Uhhuh answered 8/11, 2022 at 6:26 Comment(0)
H
1

you can use this directive directly in your component.

in HTML

<mat-menu class="date-range-menu" #dateTimeMenu="matMenu">
    <div fxLayout="row">
        <div fxLayout="column" mat-filter-item>
            <b>From</b>
            <mat-calendar></mat-calendar>
        </div>
        <div fxLayout="column" mat-filter-item >
            <b>To</b>
            <mat-calendar></mat-calendar>
        </div>
    </div>
</mat-menu>

save it as filter.directive.ts import { Directive, HostListener, HostBinding } from "@angular/core";

@Directive({
  selector: "[mat-filter-item]"
})
export class FilterItemDirective {
  @HostListener("click", ["$event"])
  onClick(e: MouseEvent) {
    e.stopPropagation();
    e.preventDefault();

    return false;
  }
}
Higgs answered 10/1, 2020 at 20:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.