我在focusout()
上发生了element1
事件,在click()
上发生了element2
事件,并且当element1
失去焦点是因为在{ {1}},仅触发聚焦,而没有单击事件。
[在jQuery] [1]上工作正常,但在Angular中效果不佳。
我找到了解决方法,方法是添加一个element2
,它也适用于角度。不幸的是我做不到。
非常感谢另一个建议。
请使用setTimeout查找代码:
window.setTimeout()
答案 0 :(得分:4)
点击事件有问题。
点击事件包含2个事件,mousedown和mouseup。
您所遇到的事件的顺序是这样
1)按下鼠标 2)聚焦 3)mouseup
在1和3发生点击事件的地方。
当页面上显示其他元素(例如错误消息)并且应该在其上进行单击的按钮从其原始x和y坐标移动时,可能会发生这种情况。因此,mouseup发生在其他地方,而不是发生mousedown的地方。
基本上,我认为您的鼠标按下有效,焦点对准有效,而mouseup无效。
解决方案是使用mousedown事件而不是click。因此,您的点击不应该等待mouseup起作用。
示例:
<input type="text" (focusout)="someMethod()">
<button (mousedown)="someMethod()">Click Me!</button> //Changed (click) to (mousedown)
希望这会有所帮助。
答案 1 :(得分:0)
我开发了一种不需要'setTimeout'的解决方案,并且不会强迫您使用'mouseup'事件而不是click事件。这是用户友好的,因为单击事件“通过释放鼠标之前将鼠标从按钮上移开,使用户有机会中止单击”。 (通过野餐发表评论)
如answer by Vinod中所述,这是事件发生时间上的问题:
我的解决方案是一条指令,该指令公开在mousedown和mouseup事件之后发生的延迟对焦事件。。因此,在(延迟的)focusout事件的事件处理程序更改了click事件之前,将注册click事件。按钮的位置。
这由BehaviourSubject完成,它存储鼠标当前是否处于按下状态。当鼠标按下时注册聚焦事件时,我们不会立即触发延迟的聚焦事件(否则我们将遇到同样的老问题)。相反,我们等待鼠标再次返回,然后发出延迟的聚焦事件。这导致以下顺序:
该指令的用法如下:
<input appDelayedFocusout (delayedFocusout)="yourLayoutChangingHandler()">
我的指令实现利用until-destroy库来防止内存泄漏永远不会终止订阅,但可以随时进行修改。
import {Directive, EventEmitter, HostListener, OnInit, Output} from '@angular/core';
import {BehaviorSubject, fromEvent} from 'rxjs';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, map, take} from 'rxjs/operators';
/**
* This directive exposes a special variant of the 'focusout' event. The regular 'focusout' event has a quirk:
* Imagine the user clicks on some button on the page. This triggers the following events in the following order:
* mousedown, focusout, mouseup. But the focusout event handler might change the layout of the website so that
* the button on which the mousedown event occurred moves around. This leads to no mouseup event registered on
* that button. Therefore a click event is also not registered because a click event consists of
* a mousedown AND a mouseup event on that button. In order to fix that problem, this directive exposes a delayed focusout
* event that is triggered AFTER the mousedown and mouseup events. When the delayed focusout event handler changes
* positions of buttons, click events are still registered as you would expect.
*/
@UntilDestroy()
@Directive({
selector: '[appDelayedFocusout]'
})
export class DelayedFocusoutDirective implements OnInit {
@Output() delayedFocusout = new EventEmitter<boolean>();
isMouseDownSubject = new BehaviorSubject(false);
ngOnInit(): void {
fromEvent(document.body, 'mousedown').pipe(untilDestroyed(this))
.subscribe(() => this.isMouseDownSubject.next(true));
fromEvent(document.body, 'mouseup').pipe(untilDestroyed(this))
.subscribe(() => this.isMouseDownSubject.next(false));
}
@HostListener('focusout') onFocusout() {
// If the mouse is currently down, we subscribe to the the event of
// 'mouse being released' to then trigger the delayed focusout.
// If the mouse is currently not down, we can trigger the delayed focusout immediately.
if (this.isMouseDown()) {
this.mouseRelease().subscribe(() => {
// This code is executed once the mouse has been released.
this.delayedFocusout.emit(true);
});
} else {
this.delayedFocusout.emit(true);
}
}
/**
* Emits the value true once the mouse has been released and then completes.
* Also completes when the mouse is not released but this directive is being destroyed.
*/
mouseRelease() {
return this.isMouseDownSubject.pipe(
untilDestroyed(this),
// Just negate isDown to get the value isReleased.
map(isDown => !isDown),
// Only proceed when the the mouse is released.
filter(isReleased => isReleased),
take(1)
);
}
isMouseDown() {
return this.isMouseDownSubject.value;
}
}