How to prevent angular material mat-menu from closing?
JavascriptAngularAngular Material2Javascript Problem Overview
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
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?
Javascript Solutions
Solution 1 - Javascript
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>
Solution 2 - Javascript
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
}
Solution 3 - Javascript
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>
Solution 4 - Javascript
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
Solution 5 - Javascript
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.
Solution 6 - Javascript
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;
}
}