Angular 2:实现自定义上下文菜单

Angular 2:实现自定义上下文菜单,angular,Angular,我正在实现Angular 2属性指令,允许我向如下元素添加自定义上下文菜单: <p context-menu="myItems">Hello world</p> 你好,世界 该指令添加了一个鼠标事件处理程序来捕获右键单击,其思想是构造一个上下文菜单,将其添加到DOM中,然后在用户完成后销毁它 我有一个实现上下文菜单本身的组件。我想构造该组件,调用其上的方法来设置项列表,然后将其添加到DOM中 看起来我可能可以使用AppViewManager.createHostVie

我正在实现Angular 2属性指令,允许我向如下元素添加自定义上下文菜单:

<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;
  }
}