处理嵌套组件时如何停止触发“点击”事件

时间:2020-05-18 15:20:42

标签: javascript angular dom-events stoppropagation

我要做什么:

  • 创建一个“调整大小”角度指令,该指令允许用户拖动DOM元素的边界并调用自定义应用程序逻辑(完成)
  • 确保点击不会冒出父级组件。

stackblitz链接-> stackblitz example

组件层次结构就是这样

  • app.component> parent.component> child.component(子组件使用'resize'指令)

在“ resize.directive”中。

  • 捕获到mousedown事件,并使用event.stopPropagation()event.preventDefault()防止事件冒泡
  • 仅当用户的鼠标越过边界时,才会捕获mousedown事件

child.component

  • 是一个简单的div,使用'resize'指令

父组件

  • 使用@HostListener('click', ['$event'])
  • 我需要监听父容器上的“单击”事件,以识别何时选择了它。所以我不能简单地摆脱它。

当您将鼠标悬停在内部子组件上时,可以单击以拖动并调整元素边框的大小。我正在努力的是父组件中“ click”处理程序的事件传播。

我的问题是,我不希望从父组件中触发@HostListener click事件。即,如果您拖动子组件的边框。在鼠标悬停时,将触发ParentComponent的click事件。

我的理解是

  • 为防止单击事件的事件传播,应在mousedown事件中捕获该事件。因此stopPropagation()

但是父组件仍在拾取点击事件。

使用return false周围有很多评论。但是,基于其他问题,这似乎并不是原因,如此处指示。

话虽如此,即使我将return false添加到指令中,事件仍然冒泡。

任何对我可能缺少的见解的赞赏。

1 个答案:

答案 0 :(得分:0)

为澄清起见,事件监听器将从事件发生的元素开始按以下顺序触发

directive -> child -> parent -> document

据我所知,您遇到的问题归结为多种因素:

  1. 拖动边框时,鼠标最终将位于子元素之外,因此最终将触发parent.mouseup -> document.mouseup。要捕获指令中的事件,您必须收听document.mouseup

  2. 您的指令正在监听document.mouseupparent.mouseupdocument.mouseup之前被触发,因此您无法在指令中停止传播。

让我们看看将边框向下拖动会发生什么:

  1. 您在边框上按。这将触发directive.mousedown -> child.mousedown -> ...。您停止在指令中进行传播,所以一切都很好。

  2. 将鼠标拖动到底部,光标现在位于子元素的外部。

  3. 您释放鼠标。请注意,我们不再位于child元素中,因此将触发parent.mouseup -> document.mouseup。父处理程序将在文档处理程序之前运行,因此您无法停止它。

  4. 由于downup发生在父控制器中,因此现在触发了单击。同样,我们不在子元素之内,因此触发器为parent.click -> document.click。您无法真正阻止这种情况的发生,因为父级是FIRST处理程序。

我可能会提出以下解决方案,尽管它似乎有些怪异:

  1. 我们可以使用自己的滚动方式来代替使用HostListener('click')触发父级点击:我们将监听parent.mousedownparent.mouseup。如果它们连续快速发生,则假定是单击,因此我们“选择”了父级。
// parent.component.ts
export class ParentComponent {
  parentClicked: boolean = false;
  down?: number;


  manualClick(): void {
    this.parentClicked = true;
    console.log(`parent manual click`);
  }

  @HostListener('mousedown', ['$event'])
  onMouseDown( event: MouseEvent): void {
    console.log('parent down', event.target);
    this.down = Date.now();
  }


  @HostListener('mouseup', ['$event'])
  onMouseUp( event: MouseEvent): void {
    console.log('parent up');
    // Assume that if it's released within 100 ms, it's a click.
    if (this.down && Date.now() - this.down < 100) {
      this.manualClick();
      this.down = undefined;
    }
  }
}

由于我们不听点击事件,所以现在很好:

  1. 按下边界并停止该指令中的传播,因此parent.mousedown从未发生。
  2. 您将鼠标拖动到孩子的外部并进入父元素。
  3. 您释放鼠标,这会触发parent.mouseup -> document.mouseup。由于parent.mousedown未被触发,down = undefined,因此我们的“点击逻辑”不会将其计为一次点击。
  4. 该事件最多冒出document.mouseup,您最终在指令中对其进行处理。

现在,这里存在一个小的潜在问题:当您单击子元素时,您将触发父级的点击逻辑,因为 child.mousedown-> parent.mousedown后跟child.mouseup -> parent.mouseup。如果您不希望发生这种情况,可以停止在子级中传播mousedown事件:

// child.component.ts
  @HostListener('mousedown', ['$event'])
  onMouseDown( event: MouseEvent): void {
    console.log('child down');
    event.stopPropagation();
    event.preventDefault();
  }