Using `fyzMatMenuTrigger`
@flyze/lib-core-angular 1.0.0-alpha.34· latestHow to open a Material menu with a gap, a viewport margin, or from an arbitrary element or coordinate.
Behavior and limitations: features/mat-menu-trigger.
Import it
Section titled “Import it”FyzMenuTrigger is a standalone directive, so import the class — there is no module:
@Component({ standalone: true, imports: [MatMenuModule, FyzMenuTrigger], …})In an NgModule-based component, add FyzMenuTrigger to the module’s imports (not
declarations). MatMenuModule is still required — the menu itself is Material’s.
Replace the Material trigger
Section titled “Replace the Material trigger”<button [fyzMatMenuTriggerFor]="menu" [gap]="8" [viewportMargin]="16">Actions</button>
<mat-menu #menu="matMenu" yPosition="below"> <button mat-menu-item>Rename</button> <button mat-menu-item>Delete</button></mat-menu>That is the whole difference from matMenuTriggerFor. gap defaults to 0, viewportMargin to
8. xPosition / yPosition on the <mat-menu> work as before, and so do menuOpened,
menuClosed, closeMenu() and menuOpen.
The gap is applied along the vertical axis — yPosition="below" pushes the menu down by gap,
"above" pushes it up. It holds in every position the menu can take, including the fallback the CDK
picks when your preferred side does not fit.
Open from an element that is not the trigger
Section titled “Open from an element that is not the trigger”Useful when many buttons share one menu, or when the visual anchor is not the element that was
clicked. Declare a trigger with no for, grab it, and point it at an element:
<div class="invisible" #trigger="fyzMatMenuTrigger" fyzMatMenuTrigger></div>
<button (click)="open($event, menu)">Row 1</button><button (click)="open($event, menu)">Row 2</button>
<mat-menu #menu="matMenu"><button mat-menu-item>Rename</button></mat-menu>@ViewChild('trigger') private trigger: FyzMenuTrigger;
open(event: MouseEvent, menu: MatMenu) { if (!(event.target instanceof HTMLElement)) return;
this.trigger.menu = menu; this.trigger.openMenuAtElement(event.target);}Assign menu before opening — that is what lets one trigger serve several menus. The element is
used for that one open only; calling openMenu() afterwards falls back to the host element of
the directive.
Keep the host element in the layout (visibility: hidden, or a zero-size box) rather than
display: none, so Material still has an element to attach to.
Open at a coordinate
Section titled “Open at a coordinate”onContextMenu(event: MouseEvent) { event.preventDefault(); this.trigger.menu = this.contextMenu; this.trigger.openMenuAt(event.clientX, event.clientY);}Viewport coordinates. A menu opened this way stays pinned to the point rather than following anything — which is what a context menu should do.
Anchor the menu to a wrapper instead of the trigger
Section titled “Anchor the menu to a wrapper instead of the trigger”When the trigger is not the box the menu should line up with — a search input inside a chips
container, for example — point anchor at the wrapper. The menu then attaches to the wrapper on
every open, while the trigger, its click handling and its ARIA attributes stay on the input:
<div #wrapper class="chips-field"> <span class="chip">Kitchen</span> <input [fyzMatMenuTriggerFor]="menu" [anchor]="wrapper" /></div>A template ref (#wrapper) or an ElementRef from @ViewChild both work. Without it the menu
would line up with the input, drifting further right as each chip pushes the input along.
Do not try to do this with
openMenuAtElement()from(menuOpened). That event fires from insideopenMenu(), so the nestedopenMenu()early-returns and the element is applied to the next open instead of the current one — the first open looks wrong and the second looks right. Useanchor.
openMenuAtElement() still wins for the one open it is called for, so the two compose: a persistent
anchor for normal opens, an explicit element for a one-off.
Open the menu yourself
Section titled “Open the menu yourself”By default a click on the trigger toggles the menu, exactly as Material does. For a combobox that is
usually wrong — toggleMenu() means clicking the input to move the caret closes the list. Turn the
click handling off and drive it from your own handlers:
<input #trigger="fyzMatMenuTrigger" [fyzMatMenuTriggerFor]="menu" [openOnClick]="false" (focus)="trigger.openMenu()" (keydown)="onKeydown($event, trigger)"/>The menu stays linked, so aria-haspopup, aria-expanded and aria-controls keep working — which
is why this is better than the tempting [fyzMatMenuTriggerFor]="disabled ? null : menu". Nulling
the menu to stop it opening also strips those attributes; [openOnClick]="!disabled" does not.
openOnClick only covers the click. A submenu trigger still opens on hover and RIGHT_ARROW.
Close the menu when the trigger scrolls away
Section titled “Close the menu when the trigger scrolls away”An open menu follows its trigger, including inside a nested overflow: auto container you have not
marked cdkScrollable. Once the trigger scrolls out of sight the menu keeps following it and is
clamped to the viewport edge — Material’s behavior. If you would rather have the menu go away:
<button [fyzMatMenuTriggerFor]="menu" closeOnScrollOutOfView>Actions</button>It works as a bare attribute or as a binding, and it checks the trigger against the viewport as well as against every scroll container it sits in.
Menus opened with openMenuAt(x, y) are the exception: they stay pinned to the point, so there is
nothing to follow.
What does not work
Section titled “What does not work”- Nothing behind an open menu is interactive. Material’s backdrop swallows clicks and wheel events, so you cannot scroll the page underneath → proposals/menu-trigger-scroll-pass-through. This is worth knowing when you try to test the scroll behavior by hand: nothing outside the menu can be wheeled while it is open.
Verify
Section titled “Verify”The /fyz-mat-menu-trigger route of the test app has all three usages — per-button triggers, one
shared trigger opened at the clicked element, and one trigger with one menu repositioned per button
— plus a window-scroll row, two scroll boxes, and a chips field using anchor with
[openOnClick]="false".
The scroll boxes have a “start auto scroll” button because a backdrop-covered page cannot be wheeled: start the scroll, then open the menu inside the box.