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

Ben*_*lts 12 angular

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

<p context-menu="myItems">Hello world</p>
Run Code Online (Sandbox Code Playgroud)

该指令添加了一个鼠标事件处理程序来捕获右键单击,然后想法是构造一个上下文菜单,将其添加到DOM,然后在用户完成时将其销毁.

我有一个实现上下文菜单本身的组件.我想构造该组件,在其上调用一个方法来设置项目列表,然后将其添加到DOM.

看起来我可以用AppViewManager.createHostViewInContainer来做到这一点.这是一个合适的方法吗?如果是这样,有没有办法构造/获取ElementRef,document.body以便我可以告诉createHostViewInContainer在那里构建组件?显然我不希望我的菜单被剪切到我正在添加上下文菜单的元素中.

Abd*_*yer 27

我认为这是一个很好的方法.
您需要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[]}>();
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到提供商列表中 bootstrap()

bootstrap(AppComponent,[ContextMenuService]);
Run Code Online (Sandbox Code Playgroud)

组件ContextMenuHolderComponent:

  • 此组件添加到根组件内.例如AppComponent,它有一个fixed位置.
  • 它订阅subjectin ContextMenuService接收:

    1. 菜单项类型{title:string,subject:Subject}[],主题用于发送菜单内的点击值
    2. MouseEvent对象
  • 它有一个(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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

并将其添加到根组件:

@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 { }
Run Code Online (Sandbox Code Playgroud)

最后一个,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
  }
}
Run Code Online (Sandbox Code Playgroud)

而已.您现在需要做的就是将[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;
  }
}
Run Code Online (Sandbox Code Playgroud)

  • @Zhytkevich这只是给`show`分配一个新主题.投诉是因为我没有指定主题`类型`.你可以通过`public show:Subject <{event:MouseEvent,obj:any []}> = new Subject <{event:MouseEvent,obj:any []}>();`来摆脱它.或者简单地将类型为'any`的主题设为`public show:Subject <any> = new Subject <any>();` (2认同)