Javascript 在Angular 2中处理click和clickOutside事件执行优先级的最佳方法?

Javascript 在Angular 2中处理click和clickOutside事件执行优先级的最佳方法?,javascript,angularjs,events,angular,Javascript,Angularjs,Events,Angular,我是Angular 2的新手,正在构建一个应用程序,它在父主机组件中生成同一子组件的多个实例。单击其中一个组件将其置于编辑模式(显示表单输入),在激活的编辑组件外部单击应将编辑组件替换为默认只读版本 @Component({ selector: 'my-app', template: ` <div *ngFor="let line of lines"> <ng-container *ngIf="!line.edit"> <rea

我是Angular 2的新手,正在构建一个应用程序,它在父主机组件中生成同一子组件的多个实例。单击其中一个组件将其置于编辑模式(显示表单输入),在激活的编辑组件外部单击应将编辑组件替换为默认只读版本

@Component({
  selector: 'my-app',
  template: `
    <div *ngFor="let line of lines">

    <ng-container *ngIf="!line.edit">
      <read-only 
        [lineData]="line"
      ></read-only>
    </ng-container>

    <ng-container *ngIf="line.edit">
      <edit 
        [lineData]="line"
      ></edit>
    </ng-container>    
    </div>
  `,
})

export class App {
  name:string;
  lines:any[];

  constructor() {
    this.name = 'Angular2';
    this.lines = [{name:'apple'},{name:'pear'},{name'banana'}];
  }
}
@组件({
选择器:“我的应用程序”,
模板:`
`,
})
导出类应用程序{
名称:字符串;
行:任何[];
构造函数(){
this.name='Angular2';
this.lines=[{name:'apple'},{name:'pear'},{name'banana'}];
}
}
只读组件上的(单击)处理程序将项目置于编辑模式,类似地,附加到自定义指令中定义的(单击外部)事件的处理程序将项目切换出编辑模式

只读组件

@Component({
  selector: 'read-only',
  template: `
    <div 
      (click)="setEditable()"
    >
     {{lineData.name}}
    </div>
  `,
   inputs['lineData']
})

export class ReadOnly {

 lineData:any;
 constructor(){

 }  

 setEditable(){
  this.lineData.edit=true;
 }
}
@Component({
  selector: 'edit',
  template: `
    <div style="
      background-color:#cccc00;
      border-width:medium;
      border-color:#6677ff;
      border-style:solid"

      (clickOutside)="releaseEdit()"
      >
    {{lineData.name}}
  `,
  inputs['lineData']
})
export class Edit {

 lineData:any;
 constructor(){

 } 

 releaseEdit(){
   console.log('Releasing edit mode for '+this.lineData.name);
   delete this.lineData.edit;
 }
}
@组件({
选择器:“只读”,
模板:`
{{lineData.name}
`,
输入['lineData']
})
导出类只读{
线条数据:任何;
构造函数(){
}  
setEditable(){
this.lineData.edit=true;
}
}
编辑组件

@Component({
  selector: 'read-only',
  template: `
    <div 
      (click)="setEditable()"
    >
     {{lineData.name}}
    </div>
  `,
   inputs['lineData']
})

export class ReadOnly {

 lineData:any;
 constructor(){

 }  

 setEditable(){
  this.lineData.edit=true;
 }
}
@Component({
  selector: 'edit',
  template: `
    <div style="
      background-color:#cccc00;
      border-width:medium;
      border-color:#6677ff;
      border-style:solid"

      (clickOutside)="releaseEdit()"
      >
    {{lineData.name}}
  `,
  inputs['lineData']
})
export class Edit {

 lineData:any;
 constructor(){

 } 

 releaseEdit(){
   console.log('Releasing edit mode for '+this.lineData.name);
   delete this.lineData.edit;
 }
}
@组件({
选择器:“编辑”,
模板:`
{{lineData.name}
`,
输入['lineData']
})
导出类编辑{
线条数据:任何;
构造函数(){
} 
releaseEdit(){
console.log('为'+this.lineData.name'释放编辑模式);
删除this.lineData.edit;
}
}
问题是切换到编辑模式的单击事件也会被clickOutside处理程序拾取。在内部,clickOutside处理程序由click事件触发,并测试编辑组件的nativeElement是否包含click目标-如果不包含,则发出clickOutside事件

单击外部指令

@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {

    ready: boolean;


    constructor(private _elementRef: ElementRef, private renderer: Renderer) {
    }

    @Output()
    public clickOutside = new EventEmitter<MouseEvent>();

    @HostListener('document:click', ['$event', '$event.target'])


    public onClick(event: MouseEvent, targetElement: HTMLElement): void {
        if (!targetElement) {
            return;
        }

        const clickedInside = this._elementRef.nativeElement.contains(targetElement);

        if (!clickedInside) {
            this.clickOutside.emit(event);
        }
    }
}
@Directive({
    selector: '[clickOutside]'
})
export class ClickOutsideDirective {
@Output() public clickOutside = new EventEmitter<MouseEvent>()

localevent:any;
lineData:any;
globalListenFunc:Function;
localListenFunc:Function;
ready:boolean;

 constructor(private elementRef:ElementRef, private renderer:Renderer){
      this._initClickOutside();
 }  


_initHandlers(){

  this.localListenFunc = this.renderer.listen(this.elementRef.nativeElement,'click',($event)=>{ 
    this.localevent = $event;

  });

}

_initClickOutside(){
  this.globalListenFunc = this.renderer.listenGlobal('document','click',($event)=>{

    if(!this.ready){
      this.ready = true;
      return;
    }

  if(!this.localListenFunc) this._initHandlers();

    const clickedInside = ($event == this.localevent);

    if(!clickedInside){
        this.clickOutside.emit($event);
        delete this.localevent;
    }
  });
}

ngOnDestroy() {
    // remove event handlers to prevent memory leaks etc.
    this.globalListenFunc();
    this.localListenFunc();
}

}
@指令({
选择器:“[单击外部]”
})
导出类ClickOutside指令{
就绪:布尔;
构造函数(私有_elementRef:elementRef,私有呈现器:呈现器){
}
@输出()
public clickOutside=new EventEmitter();
@HostListener('文档:单击',['$event','$event.target']))
public onClick(事件:MouseEvent,targetElement:HTMLElement):void{
如果(!targetElement){
返回;
}
const clickedInside=此。_elementRef.nativeElement.contains(targetElement);
如果(!clickedInside){
点击outside.emit(事件);
}
}
}
我尝试过在ngAfterContentInit()和ngAfterContentChecked()生命周期挂钩中,使用Renderer listen()将(clickOutside)事件动态绑定到编辑组件,但没有成功。我注意到本机事件可以通过这种方式绑定,但我无法绑定到自定义指令的输出

我在这里附上了一个例子来说明这个问题。使用console up单击元素可以演示如何在创建编辑组件的相同单击事件中触发(上次)clickOutside事件-立即将组件恢复为只读模式

处理这种情况最干净的方法是什么?理想情况下,我可以动态绑定clickOutside事件,但尚未找到成功的方法。

部分解决方案(依赖于DOM)

请原谅回复自己的帖子,但希望这对以类似方式挣扎的人有用

上面代码的主要问题是,定义指令(clickOutside)内部的ClickedSide设置总是测试为false

const clickedInside = this._elementRef.nativeElement.contains(targetElement);
原因是
this.\u elementRef.nativeElement
引用编辑组件的DOM元素,而
targetElement
引用最初单击的DOM元素(即只读组件),因此条件始终测试为false,触发clickOutside事件

本例中使用的简单DOM元素的一个解决方法是测试每个属性中修剪后的HTML的字符串相等性

const name = this.elementRef.nativeElement.children[0].innerHTML.trim();
const clickedInside = name == $event.target.innerHTML.trim();
第二个问题是,定义的事件处理程序将持续存在并导致问题,因此我们将更改ClickOutside指令中的click处理程序,以便在构造函数中动态创建它

export class ClickOutsideDirective {
@Output() public clickOutside = new EventEmitter<MouseEvent>()

globalListenFunc:Function; 

 constructor(private elementRef:ElementRef, private renderer:Renderer){

  // assign ref to event handler destroyer method returned
  // by listenGlobal() method here 
  this.globalListenFunc = renderer.listenGlobal('document','click',($event)=>{

    const name = this.elementRef.nativeElement.innerHTML.trim();
    const clickedInside = name == $event.target.innerHTML.trim();

    if(!clickedInside){
        this.clickOutside.emit(event);
        this.globalListenFunction(); //destroy event handler
    }
  });

 } 

}
导出类ClickOutside指令{
@Output()public clickout=new EventEmitter()
globalListenFunc:函数;
构造函数(private-elementRef:elementRef,private-renderer:renderer){
//将ref分配给返回的事件处理程序销毁程序方法
//通过这里的listenGlobal()方法
this.globalListenFunc=renderer.listenGlobal('document','click',($event)=>{
const name=this.elementRef.nativeElement.innerHTML.trim();
const clickedInside=name=$event.target.innerHTML.trim();
如果(!clickedInside){
点击outside.emit(事件);
this.globalListenFunction();//销毁事件处理程序
}
});
} 
}

待办事项

这是可行的,但其脆弱性和DOM依赖性。最好找到一个独立于DOM的解决方案,并且可能使用@View。。或@内容输入。理想情况下,它还将提供一种定义要应用的clickOutside测试条件的方法,以便clickOutside指令是真正通用和模块化的

有什么想法吗?

部分解决方案(依赖于DOM)

请原谅回复自己的帖子,但希望这对以类似方式挣扎的人有用

上面代码的主要问题是,定义指令(clickOutside)内部的ClickedSide设置总是测试为false

const clickedInside = this._elementRef.nativeElement.contains(targetElement);
原因是
this.\u elementRef.nativeElement
引用编辑组件的DOM元素,而
targetElement
引用最初单击的DOM元素(即只读组件),因此条件始终测试为false,触发clickOutside事件

在本例中使用的简单DOM元素的一个变通方法是测试修剪后的字符串是否相等