A context menu built with Angular 2 inspired by ui.bootstrap.contextMenu. Bootstrap classes are included in the markup, but there is no explicit dependency on Bootstrap. Demo
npm install ngx-contextmenu
- import ContextMenuModule into your app module
<ul>
<li *ngFor="let item of items" [contextMenu]="basicMenu" [contextMenuSubject]="item">Right Click: {{item?.name}}</li>
</ul>
<context-menu>
<ng-template contextMenuItem (execute)="showMessage('Hi, ' + $event.item.name)">
Say hi!
</ng-template>
<ng-template contextMenuItem divider="true"></ng-template>
<ng-template contextMenuItem let-item (execute)="showMessage($event.item.name + ' said: ' + $event.item.otherProperty)">
Bye, {{item?.name}}
</ng-template>
<ng-template contextMenuItem passive="true">
Input something: <input type="text">
</ng-template>
</context-menu>
@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
<ng-template>
element with thecontextMenuItem
attribute directive applied. - If the
item
object is used in the context menu item template, thelet-item
attribute must be applied to the<ng-template>
element. ** Note: ** Make sure to use theitem?.property
syntax in the template rather thanitem.property
as the item will be initiallyundefined
. - Every context menu item emits
execute
events. The$event
object is of the form{ event: MouseEvent, item: any }
whereevent
is the mouse click event that triggered the execution anditem
is the current item. - The
divider
input parameter is optional. Items default to normal menu items. Ifdivider
istrue
, all the other inputs are ignored. - The
passive
input parameter is optional. Ifpassive
istrue
, the menu item will not emit execute events or close the context menu when clicked. - The
enabled
input parameter is optional. Items are enabled by default. This can be a boolean value or a function definition that takes an item and returns a boolean. - The
visible
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.
<context-menu>
<ng-template contextMenuItem let-item [visible]="isMenuItemType1" [enabled]="false" (execute)="showMessage('Hi, ' + $event.item.name)">
Say hi, {{item?.name}}! <my-component [attribute]="item"></my-component>
With access to the outside context: {{ outsideValue }}
</ng-template>
</context-menu>
public outsideValue = "something";
public isMenuItemType1(item: any): boolean {
return item.type === 'type1';
}
- Change
package.json
to referencengx-contextmenu
instead ofangular2-contextmenu
- Upgrade to
@angular
4.x - Use
<ng-template>
instead of<template>
- Update any styles that referenced
.angular2-contextmenu
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()
.
If you need access to properties in your component from within the enabled
or visible
functions, you'll need to pass in a version of the function with this
bound to your component.
<ng-template ... [visible]="isMenuItemOutsideValueBound">
public outsideValue = "something";
public isMenuItemOutsideValueBound = this.isMenuItemOutsideValue.bind(this);
public isMenuItemOutsideValue(item: any): boolean {
return item.type === this.outsideValue;
}
You can use multiple context menus in the same component if you would like.
<ul>
<li *ngFor="let item of items" [contextMenu]="basicMenu" [contextMenuSubject]="item">{{item?.name}}</li>
</ul>
<context-menu #basicMenu>
...
</context-menu>
<ul>
<li *ngFor="let item of items" [contextMenu]="otherMenu" [contextMenuSubject]="item">{{item?.name}}</li>
</ul>
<context-menu #otherMenu>
...
</context-menu>
@ViewChild('basicMenu') public basicMenu: ContextMenuComponent;
@ViewChild('otherMenu') public otherMenu: ContextMenuComponent;
If your <context-menu>
component is in a different component from your list, you'll need to wire up the context menu event yourself.
<ul>
<li *ngFor="let item of items" (contextmenu)="onContextMenu($event, item)">Right Click: {{item.name}}</li>
</ul>
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();
}
}
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:
<ul>
<li *ngFor="let item of items" (click)="onContextMenu($event, item)">Left Click: {{item.name}}</li>
</ul>
This could be (keydown)
, (mouseover)
, or (myCustomEvent)
as well.
The html that is generated for the context menu looks like this:
<div class="dropdown ngx-contextmenu">
<ul class="dropdown-menu">
<li>
<a><!-- the template for each context menu item goes here --></a>
<span><!-- the template for each passive context menu item goes here --></span>
</li>
</ul>
</div>
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.
.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;
}
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:
@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:
<context-menu [useBootstrap4]="true"></context-menu>
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).
<context-menu (close)="processContextMenuCloseEvent()"></context-menu>
The items in the context menu are completely controlled by the contextMenuActions
object.
<ul>
<li *ngFor="item in items" [contextMenu]="myContextMenu" [contextMenuSubject]="item">Right Click: {{item.name}}</li>
</ul>
<context-menu #myContextMenu>
<template *ngFor="let action of contextMenuActions" contextMenuItem let-item
[visible]="action.visible" [enabled]="action.enabled" [divider]="action.divider"
(execute)="action.execute($event.item)">
{{ action.html(item) }}
</template>
</context-menu>
@Component({
...
})
export class MyContextMenuClass {
public items = [
{ name: 'John', otherProperty: 'Foo' },
{ name: 'Joe', otherProperty: 'Bar' }
};
@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',
},
];
}