[英]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.