Angular 2: Implement a custom context menu

后端 未结 1 1791
猫巷女王i
猫巷女王i 2021-02-02 18:26

I\'m implementing an Angular 2 attribute directive to allow me to add a custom context menu to an element like this:

Hello worl

1条回答
  •  误落风尘
    2021-02-02 18:46

    Here is what I think is a good way to do it.
    You need 1 service, 1 component and 1 directive.

    Here is a plunker

    Explanation:

    The service ContextMenuService:

    • provides a subject of type {event:MouseEvent,obj:any[]} to be subscribed to by ContextMenuHolderComponent, and to receive values from ContextMenuDirective

    Code:

    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[]}>();
    }
    

    And add it to the list of providers in bootstrap()

    bootstrap(AppComponent,[ContextMenuService]);
    

    The Component ContextMenuHolderComponent:

    • This component is added inside the root component. e.g. AppComponent and it has a fixed position.
    • It subscribes to the subject in ContextMenuService to receive:

      1. menu items of type {title:string,subject:Subject}[], the subject is used to send the clicked on value inside the menu
      2. MouseEvent object
    • It has a (document:click) event listener to close the menu on clicks outside the menu.

    code:

    @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:
      `
    ` }) 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 } } }

    And add it to the root component:

    @Component({
        selector: 'my-app',
        directives:[ContextMenuHolderComponent,ChildComponent],
        template: `
        
        
    Whatever contents
    ` }) export class AppComponent { }

    The last one, ContextMenuDirective:

    • It adds a contextmenu event to the host element.
    • Accept an input of a list of items to be passed to ContextMenuHolderComponent.

    Code:

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

    That's it. All you need to do now is attach the [context-menu] directive to an element and bind it to a list of items. For example:

    @Component({
      selector:'child-component',
      directives:[ContextMenuDirective],
      template:`
      
    right click here ... {{firstRightClick}}
    Also right click here...{{secondRightClick}}
    ` }) 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; } }

    0 讨论(0)
提交回复
热议问题