Angular 2:实现自定义上下文菜单
我正在实现Angular 2属性指令,允许我向如下元素添加自定义上下文菜单:Angular 2:实现自定义上下文菜单,angular,Angular,我正在实现Angular 2属性指令,允许我向如下元素添加自定义上下文菜单: <p context-menu="myItems">Hello world</p> 你好,世界 该指令添加了一个鼠标事件处理程序来捕获右键单击,其思想是构造一个上下文菜单,将其添加到DOM中,然后在用户完成后销毁它 我有一个实现上下文菜单本身的组件。我想构造该组件,调用其上的方法来设置项列表,然后将其添加到DOM中 看起来我可能可以使用AppViewManager.createHostVie
<p context-menu="myItems">Hello world</p>
你好,世界
该指令添加了一个鼠标事件处理程序来捕获右键单击,其思想是构造一个上下文菜单,将其添加到DOM中,然后在用户完成后销毁它
我有一个实现上下文菜单本身的组件。我想构造该组件,调用其上的方法来设置项列表,然后将其添加到DOM中
看起来我可能可以使用AppViewManager.createHostViewInContainer完成此操作。这样做合适吗?如果是这样的话,有没有办法构造/获取一个ElementRef到
document.body
,这样我就可以告诉createHostViewInContainer在那里构造组件?显然,我不希望我的菜单被剪切到我正在添加上下文菜单的元素中。我认为这是一个很好的方法。您需要1个服务、1个组件和1个指令 说明: 服务
ContextMenuService
:
- 提供类型为
的主题 由{event:MouseEvent,obj:any[]}
ContextMenuHolderComponent订阅,并接收值 从
ContextMenuDirective
ContextMenuHolderComponent
:
- 此组件添加到根组件中。e、 g.
,它有一个AppComponent
固定的位置
- 它订阅
中的ContextMenuService
,以接收:主题
类型的菜单项,主题用于在菜单内发送单击的值{title:string,subject:subject}[]
- MouseEvent对象
- 它有一个
事件侦听器,可以在菜单外单击时关闭菜单(document:click)
[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;
}
}
@组件({
选择器:'child-component',
指令:[ContextMenuDirective],
模板:`
右键单击此处…{{firstRightClick}
也右键单击此处…{{secondRightClick}
`
})
类子组件{
第一次右键单击;第二次右键单击;
联系;
其他链接;
构造函数(){
此链接=[
{title:'a',subject:new subject()},
{title:'b',subject:new subject()},
{标题:'b',主题:新主题()}
];
此链接。其他链接=[
{标题:'link 1',主题:new subject()},
{标题:'link 2',主题:new subject()},
{标题:'link 3',主题:new subject()}
];
}
//订阅主题
恩戈尼尼特(){
this.links.forEach(l=>l.subject.subscribe(val=>this.firstCallback(val));
this.anotherLinks.forEach(l=>l.subject.subscribe(val=>this.secondCallback(val)))
}
第一次回调(val){
this.firstRightClick=val;
}
第二次回调(val){
this.secondRightClick=val;
}
}
在何处插入组件真的很重要吗?你可以把它放在绝对位置。我将向AppComponent
添加一个上下文菜单组件,并使用共享的全局服务发送关于它应该显示什么的说明。例如,HTML被动态地添加到主体中,而不是组件中。如果父对象有溢出:隐藏,这很重要,很多人都有。我实际上使用了位置:固定,它似乎忽略了父对象的溢出:隐藏。它感觉有点脏,但它确实完成了任务。你能解释一下=
在这里做了什么吗公共秀:主题=新主题()代码>。我找不到有关此符号的信息tsc
抱怨类型“主题”无法分配给类型“主题…”
@Zhytkevich这只是将一个新主题分配给show
。投诉是因为我没有指定主题类型。你可以通过做publicshow:Subject=newsubject()来摆脱它代码>。或者简单地通过使类型为any
的主题像public show:subject=new subject()
hi@Abdulrahman,我尝试了该应用程序,但我在可注入批注上的context-menu.service.ts处卡住了,此编译错误在作为表达式调用时无法解析类装饰器的签名。提供的参数与调用目标的任何签名都不匹配。@rish您似乎正在将参数传递到@Injectable()
装饰器中。虽然@Injectable()
不接受任何参数。@Abdulrahman thnx用于响应,但我尝试了,但当我在控制台中执行时,没有显示错误,但在浏览器中没有显示任何内容。
bootstrap(AppComponent,[ContextMenuService]);
@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 { }
@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
}
}
@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;
}
}