An Angular component to show a context menu on an arbitrary component
npm install ngx-contextmenuA context menu built with Angular (10) inspired by ui.bootstrap.contextMenu. Bootstrap classes are included in the markup, but there is no explicit dependency on Bootstrap. Demo Stackblitz example
- npm install ngx-contextmenu @angular/cdk
- import ContextMenuModule.forRoot() into your app module
- Make sure to include at the top of your index.html
Please use ngx-contextmenu@4.2.0 with Angular 5 projects.
``html`
Say hi!
Bye, {{item?.name}}
Input something:
`js`
@Component({
...
})
export class MyContextMenuClass {
public items = [
{ name: 'John', otherProperty: 'Foo' },
{ name: 'Joe', otherProperty: 'Bar' }
];
@ViewChild(ContextMenuComponent) public basicMenu: ContextMenuComponent;
}
- Each context menu item is a element with the contextMenuItem attribute directive applied.item
- If the object is used in the context menu item template, the let-item attribute must be applied to the element. item?.property
Note: Make sure to use the syntax in the template rather than item.property as the item will be initially undefined.execute
- Every context menu item emits events. The $event object is of the form { event: MouseEvent, item: any } where event is the mouse click eventitem
that triggered the execution and is the current item.divider
- The input parameter is optional. Items default to normal menu items. If divider is true, all the other inputs are ignored.passive
- The input parameter is optional. If passive is true, the menu item will not emit execute events or closeenabled
the context menu when clicked.
- The input parameter is optional. Items are enabled by default.visible
This can be a boolean value or a function definition that takes an item and returns a boolean.
- The input parameter is optional. Items are visible by default. This property enables you to show certain context menu items based on what the data item is.
This can be a boolean value or a function definition that takes an item and returns a boolean.
- Within the template, you have access to any components and variables available in the outer context.
`html`
Say hi, {{item?.name}}!
With access to the outside context: {{ outsideValue }}
`js`
public outsideValue = "something";
public isMenuItemType1(item: any): boolean {
return item.type === 'type1';
}
You can specify sub-menus like this:
`html`
Say...
...hi!
...hola!
...salut!
Bye, {{item?.name}}
Input something:
Notes:
1. The sub can not be placed inside the that references it.
2. Sub-menus may be nested as deeply as you wish.
1. Change package.json to reference ngx-contextmenu instead of angular2-contextmenu@angular
2. Upgrade to 4.x
3. Use instead of .angular2-contextmenu
4. Update any styles that referenced to use .ngx-contextmenu instead
Note: The imperative way of declaring context menu items has been removed. i.e. You can't pass an actions property to contextMenuService.show.next().
and enabled functionsIf you need access to properties in your component from within the enabled or visible functions, you can pass in an arrow function.
`html``js`
public outsideValue = "something";
public isMenuItemOutsideValue = (item: any): boolean => {
return item.type === this.outsideValue;
}
`html
...
`js
@ViewChild('basicMenu') public basicMenu: ContextMenuComponent;
@ViewChild('otherMenu') public otherMenu: ContextMenuComponent;
`Context Menu In a Different Component
If your component is in a different component from your list, you'll need to wire up the context menu event yourself.`html
- Right Click: {{item.name}}
``js
import { ContextMenuService } from 'ngx-contextmenu';@Component({
...
})
export class MyContextMenuClass {
public items = [
{ name: 'John', otherProperty: 'Foo' },
{ name: 'Joe', otherProperty: 'Bar' }
];
// Optional
@Input() contextMenu: ContextMenuComponent;
constructor(private contextMenuService: ContextMenuService) {}
public onContextMenu($event: MouseEvent, item: any): void {
this.contextMenuService.show.next({
// Optional - if unspecified, all context menu components will open
contextMenu: this.contextMenu,
event: $event,
item: item,
});
$event.preventDefault();
$event.stopPropagation();
}
}
`Triggering the Context Menu with a Different Event
The context menu can be triggered at any point using the method above. For instance, to trigger the context menu
with a left click instead of a right click, use this html:`html
- Left Click: {{item.name}}
`This could be
(keydown), (mouseover), or (myCustomEvent) as well.Positioning the Context Menu around an element
If you want to override the context menu positioning to be appended to an element instead of based on mouse position,
provide an anchorElement to the contextMenuService. This makes sense if you want to trigger the context menu with
a non-MouseEvent.`ts
public onContextMenu($event: KeyboardEvent, item: any): void {
this.contextMenuService.show.next({
anchorElement: $event.target,
// Optional - if unspecified, all context menu components will open
contextMenu: this.contextMenu,
event: $event,
item: item,
});
$event.preventDefault();
$event.stopPropagation();
}
`Custom Styles
The html that is generated for the context menu looks like this:
`html
`You can key off of the
ngx-contextmenu class to create your own styles. Note that the ul.dropdown-menu will have inline styles applied for position, display, left and top so that it will be positioned at the cursor when you right-click.`css
.ngx-contextmenu .dropdown-menu {
border: solid 1px chartreuse;
background-color: darkgreen;
padding: 0;
}
.ngx-contextmenu li {
display: block;
border-top: solid 1px chartreuse;
text-transform: uppercase;
text-align: center;
}
.ngx-contextmenu li:first-child {
border-top:none;
}
.ngx-contextmenu a {
color:chartreuse;
display: block;
padding: 0.5em 1em;
}
.ngx-contextmenu a:hover {
color:darkgreen;
background-color:chartreuse;
}
`Bootstrap 4
If you're using Bootstrap 4, you can specify a
useBootstrap4 property in the forRoot function of the ContextMenuModule in order to get the appropriate class names. Like this:`js
@NgModule({
import: [
ContextMenuModule.forRoot({
useBootstrap4: true,
}),
],
})
export class AppModule {}
`Or, if you want to repeat yourself, you can add a
useBootstrap4 attribute to each context-menu component. Like this:`html
`Different styling on menus
If you want to style one menu differently than other menus, you can add a custom style to the menu.
`html
`Please note that the style needs to be global to affect the menu, since the menu element is added to the page outside the component that triggers the menu.
AutoFocus
You can optionally set focus on the context menu whenever it opens. This enables a user to easily tab through the context menu items and press enter to select them.
`js
@NgModule({
import: [
ContextMenuModule.forRoot({
autoFocus: true,
}),
],
})
export class AppModule {}
`Keyboard navigation
You can use the keyboard to manipulate the context menu. Note: Keyboard navigation should be used in conjunction with
autoFocus, since key events are only captured when the context menu is focused.| Key | Action |
|:--------------:|------------------------------------------------|
| ArrowDown | Move to next menu item (wrapping) |
| ArrowUp | Move to previous menu item (wrapping) |
| ArrowRight | Open submenu of current menu item if present |
| ArrowLeft | Close current menu unless already at root menu |
| Enter \| Space | Open submenu or execute current menu item |
| Esc | Close current menu |
Disable Context Menu
If you need to disable the context menu, you can pass a
boolean to the [disabled] input:`html
`Close event emitter
There is a
(close) output EventEmitter that you can subscribe to for notifications when the context menu closes (either by clicking outside or choosing a menu item).`html
`Dynamic context menu
The items in the context menu are completely controlled by the
contextMenuActions object.`html
- Right Click: {{item.name}}
[visible]="action.visible" [enabled]="action.enabled" [divider]="action.divider"
(execute)="action.click($event.item)">
{{ action.html($event.item) }}
``ts
@Component({
...
})
export class MyContextMenuClass {
public items = [
{ name: 'John', otherProperty: 'Foo', type: 'type1' },
{ name: 'Joe', otherProperty: 'Bar', type: 'type2' }
];
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
public contextMenuActions = [
{
html: (item) => Say hi!,
click: (item) => alert('Hi, ' + item.name),
enabled: (item) => true,
visible: (item) => item.type === 'type1',
},
{
divider: true,
visible: true,
},
{
html: (item) => Something else,
click: (item) => alert('Or not...'),
enabled: (item) => false,
visible: (item) => item.type === 'type1',
},
];
}
``