tatol
tatol

Reputation: 303

mat-menu and button in different components

I have

<mat-menu #saveMenu="matMenu">...</mat-menu>

in app-save-document component and

<app-save-document></app-save-document>
<button mat-icon-button [matMenuTriggerFor]="saveMenu">

in another component.

if I have mat-menu and button with [matMenuTriggerFor] in different components, can I do something to make the button see the menu?

now I have ERROR Error: mat-menu-trigger: must pass in an mat-menu instance.

Upvotes: 21

Views: 17375

Answers (3)

julianobrasil
julianobrasil

Reputation: 9377

Well, if you want to do something like this:

<button mat-button [matMenuTriggerFor]="menu">Menu</button>
<other-component [matMenu]="menu"></other-component>
<mat-menu #menu="matMenu">
  <button mat-menu-item>Item 1</button>
  <button mat-menu-item>Item 2</button>
</mat-menu>

You can code <other-component> like this:

import {Component,Input} from '@angular/core';
import {MatMenu} from '@angular/material/menu';

@Component({
  selector: 'other-component',
  template: `
    This button is in another component:
    <button [matMenuTriggerFor]="matMenu">Click here to open menu</button>
  `,
})
export class OtherComponent {
  @Input() matMenu: MatMenu;
}

You can see the above example working at this stackblitz demo.


Another approach

Another approach is (I think this is what you want): your trigger button is inside the parent (but outside the child) and the menu itself is defined inside the child component.

Parent component:

<button mat-button [matMenuTriggerFor]="childComponentMenu?.menu">
    Menu in other component
</button>
<child-component></child-component>
export class ParentComponent {
  @ViewChild(ChildComponent) childComponentMenu: ChildComponent;
}

Child Component:

@Component({
  selector: 'child-component',
  template: `
    <mat-menu>
      <button mat-menu-item>Item 1 (inside other component)</button>
      <button mat-menu-item>Item 2 (inside other component)</button>
    </mat-menu>
  `,
})
export class ChildComponent {
  @ViewChild(MatMenu, {static: true}) menu: MatMenu;
}

Yet Another approach

Another approach, similar to the above one, but using template reference variables (notice the exportAs in the decorator of the child component):

Parent component:

<button mat-button [matMenuTriggerFor]="x.menu">
    Menu in other component
</button>
<child-component #x="menuInOtherComponent"></child-component>

export class ParentComponent {
}

Child Component:

@Component({
  selector: 'child-component',
  template: `
    <mat-menu>
      <button mat-menu-item>Item 1 (inside other component)</button>
      <button mat-menu-item>Item 2 (inside other component)</button>
    </mat-menu>
  `,
  exportAs: 'menuInOtherComponent',
})
export class ChildComponent {
  @ViewChild(MatMenu, {static: true}) menu: MatMenu;
}

Stackblitz demo

Upvotes: 58

Vikram Sapate
Vikram Sapate

Reputation: 1297

I was using @julianobrasil's second approach but in my case, my requirement was to close dialog on clicking on cross icon which is in the menu component (child-component). So, used this approch...

<button [matMenuTriggerFor]="menu" [matMenuTriggerData]="{ 'data': 'someData' }">

<mat-menu #menu="matMenu" [hasBackdrop]="false">
  <ng-template matMenuContent let-data="data">
    <child-comp 
      (close)="onCloseMenu()"
      [input-to-child-comp]="data"
    >
    </child-comp>
  </ng-template>
</mat-menu>

[hasBackdrop]="false" avoid closing menu on outside click.

I've used ng-template to pass some data to child menu component.

In the child component I've used one button to close the menu,

  <mat-icon class="material-icons-outlined" (click)="closeMenu()">close</maticon>

In closeMenu function just emitting close event, to close the menu,

 @Output() close = new EventEmitter();
  closeMenu() {
    this.close.emit();
  }

In parent compoent,

  @ViewChild(MatMenuTrigger) menu: MatMenuTrigger;
  onCloseMenu() {
    this.menu.closeMenu();
  }

& it's working fine.

Upvotes: 1

Benjamin M
Benjamin M

Reputation: 24557

Here's another solution using ng-content. That's my preferred way to go.

my-custom-menu-component html:

<div [matMenuTriggerFor]="menu">
  <ng-content></ng-content>
</div>

<mat-menu #menu="matMenu">
  menu content
</mat-menu>

parent-component html:

<my-custom-menu-component>
  <button>click me</button>
<my-custom-menu-component>

Upvotes: 5

Related Questions