繁体   English   中英

Angular Click Outside Directive问题以及额外的点击事件

[英]Angular Click Outside Directive issue with an extra click event

我已经实现了一个自定义的Click Outside Directive,它用于关闭模式对话框,通知,弹出窗口等(为简单起见,我们称它们为“弹出窗口”)。 但是,我正在努力为通过鼠标单击(即按钮单击)打开的弹出窗口和通过其他操作(即鼠标悬停)打开的弹出窗口实现通用解决方案。

这是我面临的问题的一个Plunker示例 :mouseover事件产生的弹出窗口需要单击两次才能关闭,而“ Click Me”按钮产生的同一弹出窗口只需单击一次即可关闭。

这是指令的代码:

@Directive({
  selector: '[clickOutside]'
})
export class ClickOutsideDirective {
  @Output('clickOutside') clickOutsideEvent = new EventEmitter();

  private globalClick: Subscription;

  constructor(private elRef: ElementRef) { }

  ngOnInit() {
    this.globalClick = Observable
      .fromEvent(document, 'click')
      .skip(1)
      .subscribe((event: MouseEvent) => {
        this.clicked(event);
      });
  }

  ngOnDestroy() {
    this.globalClick.unsubscribe();
  }

  private clicked(event: MouseEvent) {
    let clickedInside = this.elRef.nativeElement.contains(event.target);

    if (!clickedInside) {
      this.clickOutsideEvent.emit();
    }
  }
}

在ngOnInit()内部,我初始化了一个可观察的对象,该对象可监听文档上的单击并将事件传递给clicked()函数,该函数将检查单击是否在外部进行,如果是,则引发一个事件。 可观察对象使用跳过运算符过滤首次点击事件。 这是一种解决方法,因为如果您单击“ Click Me”按钮,则该原始click事件就是我要跳过的事件,因为它对指令中的我没有用。 如果我不跳过它,则弹出窗口将自行关闭,因为它将认为在外部单击。 但是,此替代方法有一个副作用,现在需要两次单击才能关闭弹出窗口,该弹出窗口是通过其他方式触发的,而不是通过单击来触发的(Plunker中的“鼠标悬停”示例演示了此问题)。

我正在努力想一个优雅而通用的解决方案来解决这个问题。 我可以想到多种“ hacky”解决方案:

Hacky解决方案1:我可以在“ Click Me”按钮处停止事件传播,这意味着ClickOutsideDirective将不会收到初始click事件,并且可以从可观察到的文档单击中删除.skip运算符。 从理论上讲这应该可行(没有尝试过),但我不喜欢的是指令的使用者现在需要知道他们需要停止事件传播,如果不这样做,指令将无法按设计工作。

Hacky解决方案2:将布尔值标志传递给指令,该标志可用于确定是否需要跳过第一次单击。 很容易做到,但是同样,API的用户现在需要知道他们需要设置此标志。 如果他们忘记了,该指令将无法按预期工作。

hacky解决方案3:跳过指令初始化后短时间内(例如100ms)发生的第一个事件。 这也很容易做到,该解决方案的优势在于,API的用户无需向指令提供任何其他信息(或停止任何事件传播),并且该指令将“正常工作”。 这里的问题是弄清楚在所有浏览器和硬件上可以使用的延迟时间。 延迟必须不小于实例化指令后事件到达所必需的时间,但如果用户决定在显示弹出窗口后立即单击鼠标,则该延迟必须足够大以跳过合法的用户单击。

谁能想到一个更好,更简单,更优雅的解决方案?

不要将逻辑保留在单独的指令中,而是为什么不将其保留在popup组件中,因为这是它负责关闭的原因。

更改组件,以便在弹出窗口后面有一个带有单击侦听器的固定位置div。 然后,只要单击它,就发送clickOutside事件。

import {Component, Output, EventEmitter} from '@angular/core';

@Component({
  selector: 'popup',
  styles: [`
      .background {
    z-index: 1;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    backgrouc
      }

      .modal-content {
    left: 0;
    right: 0;
    z-index: 3;
      }
  `],
  template: `
    <div class="background" (click)="close()">
      <div class="modal-content">
    Click outside to close me!
      </div>
    </div>
  `,
})
export class PopupComponent {

  @Output()
  clickOutside = new EventEmitter();

  close() {
    this.clickOutside.emit();
  }
}

这是一个工作正常的plunkr: https ://plnkr.co/edit/9QBJ0PXOg2GAUpBacgQV ? p = preview

因此,我终于找到了答案。 该解决方案不像我希望的那样干净,但是基本上我添加了一个可观察的计时器(值为零),它将触发可观察的.fromEvent(document,'click')。 我已经在所有现代浏览器(包括iPad和Android Chrome)中对此进行了测试,它似乎在任何地方都可以正常工作。

ngOnInit() {
   this.globalClick = Observable
      .timer(0)
      .switchMap(() => {
          return Observable.fromEvent(this.document, 'click');
      })
      .subscribe((event: MouseEvent) => {
        this.clicked(event);
      });
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM