我实现了Angular 2属性指令,允许我向这样的元素添加自定义上下文菜单:
<p context-menu="myItems">Hello world</p>
该指令添加了一个鼠标事件处理程序来捕获右键单击,然后想法构建一个上下文菜单,将其添加到DOM,然后在用户完成时将其销毁。
我有一个实现上下文菜单本身的组件。我想构建该组件,在其上调用一个方法来设置项目列表,然后将其添加到DOM中。
看起来我可以用AppViewManager.createHostViewInContainer来做到这一点。这是一个合适的方法吗?如果是这样,有没有办法构造/获取ElementRef到document.body
,以便我可以告诉createHostViewInContainer在那里构建组件?显然,我不希望我的菜单被剪切到我添加上下文菜单的元素中。
答案 0 :(得分:26)
我认为这是一个很好的方法。
您需要1个服务,1个组件和1个指令。
说明:
服务
ContextMenuService
:
{event:MouseEvent,obj:any[]}
类型的主题
由ContextMenuHolderComponent
订阅,并接收值
来自ContextMenuDirective
代码:
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Rx';
@Injectable()
export class ContextMenuService{
public show:Subject<{event:MouseEvent,obj:any[]}> = new Subject<{event:MouseEvent,obj:any[]}>();
}
并将其添加到bootstrap()
bootstrap(AppComponent,[ContextMenuService]);
组件
ContextMenuHolderComponent
:
AppComponent
并且它有一个fixed
位置。订阅subject
中的ContextMenuService
即可获得:
{title:string,subject:Subject}[]
的菜单项,主题用于发送菜单中点击的值它有一个(document:click)
事件监听器,用于关闭菜单外点击的菜单。
代码:
@Component({
selector:'context-menu-holder',
styles:[
'.container{width:150px;background-color:#eee}',
'.link{}','.link:hover{background-color:#abc}',
'ul{margin:0px;padding:0px;list-style-type: none}'
],
host:{
'(document:click)':'clickedOutside()'
},
template:
`<div [ngStyle]="locationCss" class="container">
<ul>
<li (click)="link.subject.next(link.title)" class="link" *ngFor="#link of links">
{{link.title}}
</li>
</ul>
</div>
`
})
class ContextMenuHolderComponent{
links = [];
isShown = false;
private mouseLocation :{left:number,top:number} = {left:0;top:0};
constructor(private _contextMenuService:ContextMenuService){
_contextMenuService.show.subscribe(e => this.showMenu(e.event,e.obj));
}
// the css for the container div
get locationCss(){
return {
'position':'fixed',
'display':this.isShown ? 'block':'none',
left:this.mouseLocation.left + 'px',
top:this.mouseLocation.top + 'px',
};
}
clickedOutside(){
this.isShown= false; // hide the menu
}
// show the menu and set the location of the mouse
showMenu(event,links){
this.isShown = true;
this.links = links;
this.mouseLocation = {
left:event.clientX,
top:event.clientY
}
}
}
并将其添加到根组件:
@Component({
selector: 'my-app',
directives:[ContextMenuHolderComponent,ChildComponent],
template: `
<context-menu-holder></context-menu-holder>
<div>Whatever contents</div>
<child-component></child-component>
`
})
export class AppComponent { }
最后一个,
ContextMenuDirective
:
contextmenu
事件。ContextMenuHolderComponent
的项目列表的输入。代码:
@Directive({
selector:'[context-menu]',
host:{'(contextmenu)':'rightClicked($event)'}
})
class ContextMenuDirective{
@Input('context-menu') links;
constructor(private _contextMenuService:ContextMenuService){
}
rightClicked(event:MouseEvent){
this._contextMenuService.show.next({event:event,obj:this.links});
event.preventDefault(); // to prevent the browser contextmenu
}
}
就是这样。您现在需要做的就是将[context-menu]
指令附加到元素并将其绑定到项列表。例如:
@Component({
selector:'child-component',
directives:[ContextMenuDirective],
template:`
<div [context-menu]="links" >right click here ... {{firstRightClick}}</div>
<div [context-menu]="anotherLinks">Also right click here...{{secondRightClick}}</div>
`
})
class ChildComponent{
firstRightClick; secondRightClick;
links;
anotherLinks;
constructor(){
this.links = [
{title:'a',subject:new Subject()},
{title:'b',subject:new Subject()},
{title:'b',subject:new Subject()}
];
this.anotherLinks = [
{title:'link 1',subject:new Subject()},
{title:'link 2',subject:new Subject()},
{title:'link 3',subject:new Subject()}
];
}
// subscribe to subjects
ngOnInit(){
this.links.forEach(l => l.subject.subscribe(val=> this.firstCallback(val)));
this.anotherLinks.forEach(l => l.subject.subscribe(val=> this.secondCallback(val)))
}
firstCallback(val){
this.firstRightClick = val;
}
secondCallback(val){
this.secondRightClick = val;
}
}