Angular Material Menu

In this article we'll talk about angular material menus, basically menu displays a list of choices on a temporary surface the choices appear when user interact with the button action or some other type of control.

Importing the module

Now as always the first step is to import the appropriate module so in material.module.ts I'm gonna import MatMenuModule from @angular/material and add it to the MaterialComponents array.

import {  MatMenuModule } from "@angular/material";

const MaterialComponents = [
  MatMenuModule
];

Use MatMenuModule in HTML

Let's open app.component.html file and use <mat-menu> component, to specify the choices for the menu we use the button elements with a mat-menu-item attribute <button mat-menu-item>.

<mat-menu>
  <button mat-menu-item>Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

If you save this and take a look at the browser you'll not going to see anything that is because by itself the Mat Menu elements does not render anything it has to be triggered from a UI element or programmatically so let's add an element to open the menu I'm gonna add a new button element.

<mat-menu>
  <button mat-menu-item>Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

<button mat-flat-button>Menu</button>

Now that we have the button element we need to associate the menu with this button element and there is two steps process.

  1. First Step: On the <mat-menu> component create a template reference variable and assign to it the matMenu directive like this <mat-menu #appMenu="matMenu"> with #appMenu is the name of our template reference variable.
  2. Second Step: link the button with our template reference variable using the matMenuTriggerFor directive to our template reference variable <button mat-flat-button [matMenuTriggerFor]="appMenu">Menu</button> so in simple English we told the button that there is a menu called appMenu which can be triggered by clicking on you (button).
<mat-menu #appMenu="matMenu">
  <button mat-menu-item>Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

<button mat-flat-button [matMenuTriggerFor]="appMenu">Menu</button>

Now if you save the file and take a look again at browser we have the menu button click on the menu and the menu with choices appears. angular material menus If you have that big space between choices that's because of the margin that we had added earlier in app.component.scss remove it and you'll fix that.

button {
  margin: 0rem;
}

angular material menu item

So this is how you create a basic menu with angular material. Next let's see how to create a nested menu.

Nested menu in angular material

The first step is to create the sub menu and we're going to repeat the process of creating a menu and attaching a template reference variable so inside <mat-menu> I'm gonna add 3 menu items like this.

<mat-menu>
  <button mat-menu-item>Angular 2</button>
  <button mat-menu-item>Angular 4</button>
  <button mat-menu-item>Angular 7</button>
</mat-menu>

on the <mat-menu> compmonent I'm gonna add the reference variable which is going to be called subMenu and we assign to it a matMenu directive <mat-menu #subMenu="matMenu">.

<mat-menu #subMenu="matMenu">
  <button mat-menu-item>Angular 2</button>
  <button mat-menu-item>Angular 4</button>
  <button mat-menu-item>Angular 7</button>
</mat-menu>

This is a submenu for Angular choice in main menu so all we have to do is add the trigger directive again but this time on the first item in the main menu which is Angular.

<mat-menu #appMenu="matMenu">
  <button mat-menu-item [matMenuTriggerFor]="subMenu">Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

In simple English this Angular Choice is going to trigger this submenu if we save this final code and take a look at the browser.

<mat-menu #appMenu="matMenu">
  <button mat-menu-item [matMenuTriggerFor]="subMenu">Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

<mat-menu #subMenu="matMenu">
  <button mat-menu-item>Angular 2</button>
  <button mat-menu-item>Angular 4</button>
  <button mat-menu-item>Angular 8</button>
</mat-menu>

<button mat-flat-button [matMenuTriggerFor]="appMenu">Menu</button>

angular material submenu You can see that we have a nested menu you don't have to click on Angular when you hover on the choice the submenu of angular is opened.

Positioning the menu

To place the menu wherever we want you have xPosition and yPosition attributes so on the <mat-menu> component we can specify our xPosition and yPosition, for xPosition can take the values of before or after and yPosition can take the values of above or bellow. Let's go those values here bellow.

<mat-menu #appMenu="matMenu" xPosition="after" yPosition="above">
  <button mat-menu-item [matMenuTriggerFor]="subMenu">Angular</button>
  <button mat-menu-item>ReactJs</button>
  <button mat-menu-item>VueJs</button>
</mat-menu>

<mat-menu #subMenu="matMenu">
  <button mat-menu-item>Angular 2</button>
  <button mat-menu-item>Angular 4</button>
  <button mat-menu-item>Angular 8</button>
</mat-menu>

<button mat-flat-button [matMenuTriggerFor]="appMenu">Menu</button>

If you save a take a look at the browser you'll see something like this. Positioning the menu You can see that the menu opens to the right so xPosition is working fine but the menu still opens bellow the button which is not what we specify with yPosition="above" which means it should opens above the button and that is simply because there is no space available at the top to fix that let's reposition the button by adding this css to app.component.scss file.

.menu-button{
    margin: 10rem;
}

back to the app.component.html let's add the .menu-button class to the button element.

<button class="menu-button" mat-flat-button [matMenuTriggerFor]="appMenu">Menu</button>

Now if we go back to the browser and click on the button element you can see that the menu opens to the right which is xPosition="after" and to the top which is yPosition="above". xPosition yPosition That how we control the position of the menu using those attributes with values of after/before for xPosition and bellow/above for yPosition.

Lazy rendering

By default the menu choices are initialized even before the menu is opened that is even before clicking on the button element the choices have already been loaded for the menu. We can differ initialization until the menu is open using the <ng-template> directive and mat-menu-content attribute, this allows us to have dynamic content as many choices let's see how?

I'm gonna create a new menu with a reference variable called #lazyMenu and set it to matMenu within the menu component we are going to use the ng-template directive don't forget to add the attribute matMenuContent on this directive and within this directive we specify the menu items.

<mat-menu #lazyMenu="matMenu">
  <ng-template matMenuContent>
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Log Out</button>
  </ng-template>
</mat-menu>

Let's create two trigger buttons to this same menu.

<mat-menu #lazyMenu="matMenu">
  <ng-template matMenuContent>
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Log Out</button>
  </ng-template>
</mat-menu>
<button mat-button [matMenuTriggerFor]="lazyMenu">Rb Hosh</button>
<button mat-button [matMenuTriggerFor]="lazyMenu">HoshCoding</button>

Save the file and you see that when I click on each button the same menu appears and opened which is normal because we link the two buttons to the same menu by matMenuTriggerFor attribute.

But what I want is when I click on Rb Hosh button I need the choice to be Log Out Rb Hosh, the same when I click on HoshCoding I want the menu choice to be Log Out HoshCoding, basically we're looking at dynamic choices for that we make use of matMenuTriggerData attribute so back in app.component.html on the trigger buttons we'll add an extra attribute which is matMenuTriggerData which take an object as a value.

<button
  mat-button
  [matMenuTriggerData]="{ name: 'Rb Hosh' }"
  [matMenuTriggerFor]="lazyMenu"
>
  Rb Hosh
</button>
<button
  mat-button
  [matMenuTriggerData]="{ name: 'HoshCoding' }"
  [matMenuTriggerFor]="lazyMenu"
>
  HoshCoding
</button>

Now each button has it's own data which in this case just a simple name. So after specifying the data let's create a local variable on ng-template tag and retrieve the name using interpolation.

<mat-menu #lazyMenu="matMenu">
  <ng-template matMenuContent let-name="name">
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Log Out {{ name }}</button>
  </ng-template>
</mat-menu>

As you can see if you click on Rb Hosh it says Log Out Rb Hosh and if you click on HoshCoding button it says Log Out HoshCoding. Angular Material Lazy rendering

So what is happening is on the trigger button we specify the [matMenuTriggerData] and we specify the name so we are saying:

hey I know that this button is a trigger for a menu but when I'm triggering that menu let me pass some additional information that the menu can use to dynamically render some data.

In the <mat-menu> component we use <ng-template> tag and on the <ng-template> tag we specify the let-name="name" variable that we want to access and that can be interpolated in the button element choice.

Let's add one more property to understand how it works, to matMenuTriggerData I'm gonna add a new property called hobby for Rb Hosh the hobby is football and for HoshCoding the hobby is teaching, Now in the menu on the ng-template tag we declare another variable let-hobby="hobby" and let's add a new menu choice and bind the hobby.

<mat-menu #lazyMenu="matMenu">
  <ng-template matMenuContent let-name="name" let-hobby="hobby">
    <button mat-menu-item>Settings</button>
    <button mat-menu-item>Hobby is {{ hobby }}</button>
    <button mat-menu-item>Log Out {{ name }}</button>
  </ng-template>
</mat-menu>
<button
  mat-button
  [matMenuTriggerData]="{ name: 'Rb Hosh', hobby: 'football' }"
  [matMenuTriggerFor]="lazyMenu"
>
  Rb Hosh
</button>
<button
  mat-button
  [matMenuTriggerData]="{ name: 'HoshCoding', hobby: 'teaching' }"
  [matMenuTriggerFor]="lazyMenu"
>
  HoshCoding
</button>

angular material navigation menu As you can see when you click on Rb hosh it says Hobby is football and when you click on HoshCoding it says Hobby is teaching, it the same menu but with dynamic content.

Alright that is all about navigation and menus in angular material if you want more examples let me point to documentation using this link.