javascript 你能阻止 Angular 组件的主机点击触发吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/49415692/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 08:34:39  来源:igfitidea点击:

Can you prevent an Angular component's host click from firing?

javascriptangularangular-template

提问by Ryan Silva

I'm creating an Angular component that wraps a native <button>element with some additional features. Buttons do not fire a click event if they're disabled and I want to replicate the same functionality. i.e., given:

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

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

Is there a way for my-buttonto prevent onClick()from getting called?

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

In Angular you can listen to the host click event this way, and stop propagationof the event:

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

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

This prevents the event from bubbling to ancestor elements, but it does not stop the built-in (click)output from firing on the same host element.

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

Is there a way to accomplish this?

有没有办法做到这一点?



Edit 1:the way I'm solving this now is by using a different output called "onClick", and consumers have to know to use "onClick" instead of "click". It's not ideal.

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

Edit 2:Click events that originate on the <button>element are successfully stopped. But if you put elements inside the button tag as I have, click events on those targets do propagate up to the host. Hm, it should be possible to wrap the button in another element which stops propagation...

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

采纳答案by ConnorsFan

You could do the following:

您可以执行以下操作:

  • Redefine the clickevent of the component, and emit this event when the button is clicked
  • Set the CSS style pointer-events: noneon the component host
  • Set the CSS style pointer-events: autoon the button
  • Call event.stopPropagation()on the button click event handler
  • 重新定义click组件的事件,并在按钮被点击时发出该事件
  • pointer-events: none在组件宿主上设置 CSS 样式
  • 设置pointer-events: auto按钮上的CSS 样式
  • 调用event.stopPropagation()按钮单击事件处理程序

If you need to process the click event of other elements inside of your component, set the style attribute pointer-events: autoon them, and call event.stopPropagation()in their click event handler.

如果您需要处理组件内其他元素的点击事件,请为pointer-events: auto它们设置 style 属性,并调用event.stopPropagation()它们的点击事件处理程序。

You can test the code in this stackblitz.

您可以在此 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();
  }
}


UPDATE:

更新

Since the button can contain HTML child elements (span, img, etc.), you can add the following CSS style to prevent the click from being propagated to the parent:

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

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

Thanks to @ErikWitkowskifor his commenton this special case. See this stackblitzfor a demo.

由于@ErikWitkowski他的评论对这种特殊情况。有关演示,请参阅此 stackblitz

回答by Zze

I do not believe there is a native way to prevent the event from firing, as supported by this git issuein 2016:

我不相信有一种本地方法可以防止事件触发,正如2016 年的这个 git 问题所支持的那样:

The order of execution is red herring - the order in which an event on the same element is propagated to multiple listeners is currently undefined. this is currently by design.

Your problem is that the event exposed to the listeners is the real DOM event and calling stopImmediatePropagation() on the provided event stops execution of other listeners registered on this element. However since all the the listeners registered via Angular are proxied by just a single dom listener (for performance reasons) calling stopImmediatePropagation on this event has no effect.

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

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

回答by sabithpocker

You can use the native add and remove EventListeners. This is in no way a good solution when thinking in angular terms. Also, this won't work if you put disabledattribute in buttonas it will override eventListeners attached. A disabledclass need to be used instead. (Or else wrap buttonin a spanand use template ref #btnfrom it.)

您可以使用本机添加和删除 EventListeners。从角度考虑,这绝不是一个好的解决方案。此外,如果您将disabled属性放入,这将不起作用,button因为它会覆盖附加的 eventListeners。disabled需要使用一个类来代替。(或者button用 a包装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() {
  }

}