移动目标元素时不会触发点击处理程序

Click handler is not triggered when target element has been moved

考虑以下演示 https://stackblitz.com/edit/angular-pur1dt

我有带有同步验证器的反应式表单控件,当它失效时,错误消息显示在字段下方。

控件失去焦点时触发验证。在控件下方有一个带有点击处理程序的按钮。问题是,当我单击按钮时,控件失去焦点,进行验证,显示错误消息并将按钮向下移动。据推测,这会阻止点击处理程序的执行。有什么关于为什么会发生这种情况以及如何解决问题的建议吗?

我已经用评论更新了演示。注意:只有输入下方的按钮会重现该问题。第一次点击标题不会更新

好的,我想我明白了。您不希望在单击下方按钮时发生验证。

触发验证的原因是表单输入中的 autofocus。 Angular "By default, the value and validity of a control updates whenever the value changes." See FormControl。控件正在将 name.untouchedfalse 更改为 true

如果您向模板添加一些额外的调试绑定,您可以看到这种情况发生。 例如

<hello name="{{ name }}"></hello>
<form [formGroup]="form">
    <input formControlName="name" autofocus>
    <button (click)="onClick($event)">click OK</button>
</form>
<pre>{{form.controls.name.valid}}</pre>
<pre>{{form.controls.name.untouched}}</pre>
<p [hidden]="form.controls.name.valid || form.controls.name.untouched ">
    Invalid :)
</p>
<button (click)="onClick2($event)">click NOT ok</button>

如果你想在用户点击下方按钮时隐藏错误信息,你应该删除autofocus或者在调用onClick2()时重置表单的验证。或者,您可以在表单出错时只显示错误消息。 Angular 说 A control is untouched if the user has not yet triggered a blur event on it. 单击下方按钮会导致模糊事件。

为什么不直接将错误消息上的模板绑定更改为

<p [hidden]="!form.controls.name.valid || form.controls.name.untouched">
    Invalid :)
</p>

此问题已在 GitHub 的 issue #7113 中讨论过。这是因为在输入字段失去焦点时执行验证,在按钮的单击有机会完成并触发 click 事件之前。

一种可能的解决方法是在单击按钮时使用标志来隐藏消息,如 this stackblitz 所示。在下面的代码中,isClicking 标志在 mousedown 事件开始时设置,并在 click 事件完成时重置。

<p #errorMsg [hidden]="(errorMsg.hidden && isClicking) || form.controls.name.valid || form.controls.name.untouched ">
    Invalid :)
</p>
<button (mousedown)="onMouseDown($event)" (click)="onClick2($event)">click NOT ok</button>
export class AppComponent {

  isClicking = false;
  ...

  onMouseDown(event) {
    this.isClicking = true;
    setTimeout(() => {
      // The click action began but was not completed after two seconds
      this.isClicking = false;
    }, 2000);
  }

  onClick2(event) {
    console.log(event);
    this.name = "NOT";
    this.isClicking = false;
  }
}

您可以改进该解决方案,方法是将 setTimeout 替换为在 mousedown 事件处理程序中捕获鼠标的过程,并在捕获丢失时重置 isClicking。它会考虑用户在未完成点击的情况下离开按钮的情况。

问题似乎与 DOM 事件触发顺序有关

根据 MDN:

The click event is fired when a pointing device button (usually a mouse's primary button) is pressed and released on a single element.

https://developer.mozilla.org/en-US/docs/Web/Events/click

在给定的示例中,元素在您 blur 输入的那一刻移动 - 因为验证立即发生,显示错误并重新定位按钮。

因此,当 在按钮上方时,鼠标 处于按下状态,但是当 处于上升状态时 -- 按钮被重新定位。所以click不会在按钮上触发

有几种解决方法:

  • on mousedown 延迟错误显示
  • 隐藏错误,直到 mousedownmouseup 都发生,如果 mousedown 发生在按钮上
  • 等等

这是一个 mousedown 事件处理的例子 https://jsfiddle.net/gjbgqqpo/

希望这对您有所帮助:)

一个简单的解决方案是将 (mousedown)="false" 添加到您的关闭按钮。这样就不会发生模糊事件,因为你的按钮上的焦点事件将被你的 mousedown 处理程序取消。反过来,这不会将控件标记为已触摸,您的错误也不会显示。

<button type="button" (click)="dismiss()" (mousedown)="false">Cancel</button>

现在对于您的提交按钮,您不想取消模糊事件,但您可能想要 运行 mousedown 上的某种验证逻辑。这样想,如果按钮移动了,那就意味着表单有错误。当表单出现错误时你想做什么?就我而言,我真正想做的就是将所有控件标记为已触摸,以便显示所有验证错误。

<button type="submit" (mousedown)="form.markAllAsTouched()">Create</button>