How to change absolute position of mat-menu in angular 4 material using x and y co-ordinates?

Viewed 21591

I have following menu in angular 4

<button mat-button [matMenuTriggerFor]="menu" style="display:none;">Menu</button>
    <mat-menu #menu="matMenu">
        <button mat-menu-item *ngFor="let item of items" (click)="select(item)">
            {{ item }}
        </button>
    </mat-menu>

I am opening the menu when user selects text on screen using matMenuTrigger.openMenu();

but I want to open menu wherever user selects text. I have X and Y coordinates of user selection but how can I change position of menu?

I have tried giving Id to mat-menu and changing it's position using

element.style.position = 'absolute'
element.style.left = screenX + 'px'
element.style.top = screenY + 'px'

but it's not changing position of menu.

EDIT: I have changed position of menu by

this.matMenuTrigger.openMenu();
var element = document.getElementsByClassName('cdk-overlay-pane');
menu.style.position = "absolute";
menu.style.left = evt.pageX + 5 + 'px';
menu.style.top = evt.pageY + 5 + 'px';

where evt is mouseup event which gives co-ordinates(X,Y) of user text selection.

But, when I scroll the page , opened menu again goes back to it's original position. How can I keep menu to it's changed position on scroll??

4 Answers

In order to get this example to work in a more general case, I found it necessary to use "menu.style.position = 'fixed' (rather than "menu.style.position = 'absolute').

This is because "event.pageX" returns the coordinates relative to the viewport. Since "position = absolute" positions the button relative to its containing parent, the position of the invisible button will be correct only if its containing parent is not already nested in some other HTML element.

So, to summarize, I changed the code for menu-example.ts as follows and it now seems to work in all cases:

export class MenuIconsExample {
@ViewChild(MatMenuTrigger)
    private menuTrigger: MatMenuTrigger;

addTextToOpns: Array<String> = ["option 1", "option 2", "option 3"];
selectedOption: string = "no Option selected";
  onTextSelection(event: any):void{
    if(window.getSelection && window.getSelection().toString()){
      var menu = document.getElementById('menuBtn');
      menu.style.display = '';
      menu.style.position = 'fixed';
      menu.style.left = event.pageX + 5 + 'px';
      menu.style.top = event.pageY + 5 + 'px';

      this.menuTrigger.openMenu();  
    }

  }

  onMenuClosed():void {
    var menu = document.getElementById('menuBtn');
        if (menu) {
            menu.style.display = 'none';            
        }
  }

  addTextTo(selectedOpn): void {
    this.selectedOption = selectedOpn + ' selected';
  }

}

Are you also foolishly wandering here like I wasted 3 hours of mine manually setting the position of mat-menu just because the triggering element of mat-menu disappears from the DOM on some condition and you don't want mat-menu to be closed at that time, then instead of using ngIf use hidden property on that triggering element, it will save your ton of time of creating some hidden element, displaying it first, getting its coordinates bla bla bla, may be that will save someones life

The OP has edited the question to include the best answer for moving the menu position.

However, in the newer versions of angular, doing a document.getElementsByClassName returns a HTMLCollection and when accessing the element, it does not have the style property. I think this is because of the update to typescript.

I found this article which helped with it (Property 'style' does not exist on type 'Element' in TS)

The final result is this (note the setTimeout. This is done so that the mat menu can be rendered before it is moved)

this.matMenuTrigger.openMenu();
setTimeout(() => {
    const menuCollection = document.getElementsByClassName('cdk-overlay-pane') as HTMLCollectionOf<HTMLElement>;
    const menu = menuCollection[0];
    menu.style.position = 'absolute';
    menu.style.left = e.x + 5 + 'px';
    menu.style.top = e.y + 5 + 'px';
}, 0);
Related