【问题标题】:Can you prevent an Angular component's host click from firing?你能阻止 Angular 组件的主机点击触发吗?
【发布时间】:2018-03-21 20:11:42
【问题描述】:

我正在创建一个 Angular 组件,该组件包装原生 <button> 元素和一些附加功能。如果按钮被禁用并且我想复制相同的功能,则按钮不会触发单击事件。即,给定:

<my-button (click)="onClick()" [isDisabled]="true">Save</my-button>

my-button 有没有办法防止onClick() 被调用?

在Angular中你可以通过这种方式监听宿主点击事件,并停止事件的传播

//Inside my-button component
@HostListener('click', ['$event'])
onHostClick(event: MouseEvent) {
  event.stopPropagation();
}

这可以防止事件冒泡到祖先元素,但不会阻止内置 (click) 输出在同一宿主元素上触发。

有没有办法做到这一点?


编辑 1:我现在解决此问题的方法是使用名为“onClick”的不同输出,消费者必须知道使用“onClick”而不是“click”。这并不理想。

编辑 2: 源自 &lt;button&gt; 元素的点击事件已成功停止。但是,如果您像我一样将元素放在按钮标签内,那么这些目标上的单击事件会传播到主机。嗯,应该可以将按钮包装在另一个停止传播的元素中......

【问题讨论】:

  • (click)="condition ? onClick() : null"
  • 我希望它表现得像一个按钮——我不希望我的组件的使用者必须首先检查禁用状态。
  • this stackblitz 做你想做的事吗?
  • 关闭:我认为this stackblitz edit 对我来说会更简单一些。没错,如果禁用,我们希望停止传播主机内的所有内容。如果&lt;button&gt; 被禁用,您将无法收听(并停止)源自按钮内跨度的点击传播。但是您可以从包装按钮但在主机内部的另一个元素执行此操作 - 只要主机的某些部分没有您可以单击包装元素之外的部分。

标签: javascript angular angular-template


【解决方案1】:

您可以执行以下操作:

  • 重新定义组件的click事件,点击按钮时触发该事件
  • 在组件宿主上设置CSS样式pointer-events: none
  • 在按钮上设置 CSS 样式 pointer-events: auto
  • 在按钮单击事件处理程序上调用event.stopPropagation()

如果您需要处理组件内部其他元素的点击事件,请在其上设置样式属性pointer-events: auto,并在其点击事件处理程序中调用event.stopPropagation()

您可以测试this stackblitz中的代码。

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

@Component({
  selector: 'my-button',
  host: {
    "[style.pointer-events]": "'none'"
  },
  template: `
    <button (click)="onButtonClick($event)" [disabled]="isDisabled" >...</button>
    <span (click)="onSpanClick($event)">Span element</span>`,
  styles: [`button, span { pointer-events: auto; }`]
})
export class MyCustomComponent {

  @Input() public isDisabled: boolean = false;
  @Output() public click: EventEmitter<MouseEvent> = new EventEmitter();

  onButtonClick(event: MouseEvent) {
    event.stopPropagation();
    this.click.emit(event);
  }

  onSpanClick(event: MouseEvent) {
    event.stopPropagation();
  }
}

更新

由于按钮可以包含 HTML 子元素(spanimg 等),您可以添加以下 CSS 样式来防止点击传播到父元素:

:host ::ng-deep button * { 
  pointer-events: none; 
}

感谢@ErikWitkowski his comment 在这个特殊情况下。有关演示,请参阅 this stackblitz

【讨论】:

  • 在 OP 上查看我对您的回复。这不会阻止源自按钮内部元素的事件传播出去(如果按钮被禁用,按钮单击事件将不会触发,因此传播不会停止)。
  • 在这种情况下,宿主元素的点击事件也不会触发,正如您在测试 stackblitz 时看到的那样。 pointer-events 样式属性可防止主机捕获事件。
  • 我会接受这个答案。根据具体的用例,它可以变得更简单,但一般的想法是,如果它被禁用,你必须阻止任何点击事件到达宿主元素。
  • 这是防止主机触发事件的好方法。对我来说,直接在按钮元素上添加$event.stopPropagation(); 更加简单。 &lt;button type="button" data-toggle="collapse" (click)="isCollapsed = !isCollapsed; $event.stopPropagation();" appPreventDefault&gt;
  • 注意:如果你在按钮中有一个 标签,它会变成一个 并且问题会回来,因为现在 span 处理点击并将其传播回主机, 解决你可以添加css :host /deep/ button span { pointer-events: none; }
【解决方案2】:

我认为没有本地方法可以防止事件触发,正如 2016 年 this git issue 所支持的那样:

执行顺序是红鲱鱼 - 同一元素上的事件传播到多个侦听器的顺序当前未定义。这是目前的设计。

您的问题是向侦听器公开的事件是真正的 DOM 事件,并且在提供的事件上调用 stopImmediatePropagation() 会停止执行在此元素上注册的其他侦听器。 但是,由于通过 Angular 注册的所有侦听器都由一个 dom 侦听器代理(出于性能原因),因此对此事件调用 stopImmediatePropagation 无效。

【讨论】:

  • 如果组件上的(单击)是元素上的事件侦听器,我想要的东西是不可能的,但我希望它更像是我可以覆盖的“默认输出” :(
  • @ryansilva 我确实尝试通过一些自定义删除然后通过HostListener 自定义重新发送事件来解决此问题,但不幸的是无济于事。我希望您能找到解决方案,因为您提出的功能听起来很棒。
【解决方案3】:

您可以使用本机添加和删除 EventListener。从角度考虑,这绝不是一个好的解决方案。此外,如果您将 disabled 属性放在 button 中,这将不起作用,因为它将覆盖附加的 eventListeners。需要使用 disabled 类。 (或者将 button 包装在 span 中,并从中使用模板引用 #btn。)

StackBlitz

import { Component, OnInit, OnChanges, HostListener, Input, Output, EventEmitter, SimpleChanges, ElementRef, ViewChild } from '@angular/core';

@Component({
  selector: 'app-my-button',
  template: `<button [class.disabled]="isDisabled" #btn><span>hey</span></button>`,
  styles: [`button.disabled { opacity:0.5 }`]
})
export class MyButtonComponent implements OnInit, OnChanges {
  disableClick = e => e.stopPropagation();
  @Input() isDisabled: boolean;
  @ViewChild('btn') btn: ElementRef;
  constructor() { }

  ngOnChanges(changes: SimpleChanges) {
    if(this.isDisabled) {
      this.btn.nativeElement.addEventListener('click', this.disableClick);
    } else {
      this.btn.nativeElement.removeEventListener('click', this.disableClick);
    }
  }
  ngOnInit() {
  }

}

【讨论】:

    【解决方案4】:

    您可以尝试在捕获模式下停止事件的传播(将 true 作为 addEventListener 调用的最后一个参数,将 window 作为监听对象)。

    window.addEventListener('click', (event) => {
        let clickDisallowed = true; // detect if this click is disallowed
    
        if (event.target === this.elementRef.nativeElement && clickDisallowed) {
            event.stopImmediatePropagation();
        }
    }, true);
    

    别忘了取消订阅这个监听器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-13
      • 2018-06-28
      • 1970-01-01
      • 2015-04-28
      • 2014-07-31
      • 2017-12-02
      • 1970-01-01
      相关资源
      最近更新 更多