80

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?

Umamaheswaran
  • 3,690
  • 3
  • 29
  • 56

7 Answers7

165

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.

KyleK
  • 4,643
  • 17
  • 33
Anik
  • 2,692
  • 2
  • 22
  • 25
  • doesn't seems to work well. Dates cannot be selected when you click on them – danbord Sep 11 '19 at 14:50
  • 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 menu – Muhammad Awais Sep 03 '20 at 09:35
  • 1
    Worked for me. I added a wrapper div inside mat-menu and added `$event.stoppropagation()` on it. – Hamza Iftikhar Mar 31 '21 at 07:34
  • It worked for me for checkbox in mat-menu, Thank you so much – Kedar Km Dec 29 '21 at 05:45
  • 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 solution – Raphiki Mar 11 '22 at 11:26
  • 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. – Anik Mar 13 '22 at 03:23
  • I just used this solution to remove the mat-menu. :P – Vinay Somawat Nov 24 '22 at 06:37
17

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
}
JLazar0
  • 1,257
  • 1
  • 11
  • 22
12

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

Smaillns
  • 2,540
  • 1
  • 28
  • 40
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>
Ajay Gangarde
  • 311
  • 4
  • 2
2

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>
Vikram Kumar
  • 397
  • 3
  • 2
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;
  }
}
1

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.

Amarula
  • 142
  • 1
  • 15