简体   繁体   English

您能否阻止触发 Angular 组件的主机点击?

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

I'm creating an Angular component that wraps a native <button> element with some additional features.我正在创建一个 Angular 组件,它包装了一个具有一些附加功能的原生<button>元素。 Buttons do not fire a click event if they're disabled and I want to replicate the same functionality.如果按钮被禁用并且我想复制相同的功能,则按钮不会触发点击事件。 ie, given:即,给定:

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

Is there a way for my-button to prevent onClick() from getting called? my-button有没有办法阻止onClick()被调用?

In Angular you can listen to the host click event this way, and stop propagation of 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) output 在同一宿主元素上触发。

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".编辑 1:我现在解决这个问题的方法是使用另一个名为“onClick”的 output,消费者必须知道使用“onClick”而不是“click”。 It's not ideal.这并不理想。

Edit 2: Click events that originate on the <button> element are successfully stopped.编辑 2:成功停止源自<button>元素的单击事件。 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...嗯,应该可以将按钮包装在另一个停止传播的元素中......

You could do the following: 您可以执行以下操作:

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

If you need to process the click event of other elements inside of your component, set the style attribute pointer-events: auto on them, and call event.stopPropagation() in their click event handler. 如果需要处理组件内其他元素的click事件,请在它们上设置样式属性pointer-events: auto ,并在其click事件处理程序中调用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();
  }
}

I do not believe there is a native way to prevent the event from firing, as supported by this git issue in 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. 您的问题是暴露给侦听器的事件是真正的DOM事件,并且在提供的事件上调用stopImmediatePropagation()会停止执行在此元素上注册的其他侦听器。 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. 但是,由于通过Angular注册的所有侦听器仅由一个dom侦听器代理(出于性能原因),对此事件调用stopImmediatePropagation无效。

You can use the native add and remove EventListeners. 您可以使用本机添加和删除EventListeners。 This is in no way a good solution when thinking in angular terms. 从角度来看,这绝不是一个好的解决方案。 Also, this won't work if you put disabled attribute in button as it will override eventListeners attached. 此外,如果您将disabled属性放在button ,这将无效,因为它将覆盖附加的eventListeners。 A disabled class need to be used instead. 需要使用disabled类。 (Or else wrap button in a span and use template ref #btn from it.) (或者在span包装button并使用模板ref #btn 。)

StackBlitz 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() {
  }

}

You can try stop propagation of the event in capturing mode (look at true as the last param of addEventListener calling and window as the listening object).您可以尝试在捕获模式下停止事件的传播(将true视为 addEventListener 调用的最后一个参数,将window视为侦听对象)。

window.addEventListener('click', (event) => {
    let clickDisallowed = true; // detect if this click is disallowed

    if (clickDisallowed) {
        event.stopImmediatePropagation();
    }
}, true);

Catch click with early subscription.通过提前订阅获得点击。

Easy way would be to catch event on capture phase, but there is some problem with capture catch in a Firefox (when event target is disabled, click event comes to host on bubbling phase, not on capture).简单的方法是在捕获阶段捕获事件,但是在 Firefox 中捕获捕获存在一些问题(当禁用事件目标时,单击事件在冒泡阶段进入主机,而不是在捕获阶段)。 So we have to add event listener in constructor (not on init) to be the first subscriber and to use stopImmediatePropagation on event.所以我们必须在构造函数中(而不是在 init 上)添加事件监听器成为第一个订阅者,并在事件上使用 stopImmediatePropagation。

@Input() disabled: boolean;

constructor(private elementRef: ElementRef) {
  this.elementRef.nativeElement.addEventListener('click', this.captureClick, true);
}

ngOnDestroy() {
  this.elementRef.nativeElement.removeEventListener('click', this.captureClick, true);
}

private captureClick = (event: PointerEvent) => {
  if (this.disabled) {
    event.stopPropagation();
    event.preventDefault();
    event.stopImmediatePropagation();
    return false;
  }
  return true;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何防止长按结束时触发点击? - How can you prevent a click from firing at the end of a long press? 当我呼叫“ <element> 。点击()”? 还是解决这个问题的更好方法? - How can I prevent a click event from firing when I call “<element>.click()”? Or what's a better way to solve this issue? 防止单击功能多次触发 - Prevent on click function from firing multiple times 如何防止 map.on(&quot;click&quot;) 通过单击触摸板两次触发? - How can I prevent map.on("click") from firing twice with one touchpad click? 防止点击从触发确认对话框(在Angular 1.2 RC3中停止工作) - Prevent ng-click from firing for confirmation dialog (stopped working in Angular 1.2 RC3) Angular 2 防止子组件点击路由器导航 - Angular 2 prevent router navigation on child component click 如果点击了特定的孩子,则阻止父母点击被触发 - Prevent parent click from firing if a specific child is click 当选择了on选项时,如何防止ASP.NET下拉列表触发onclick事件? - How do you prevent a ASP.NET drop down list from firing it's onclick event when on option is selected? 如何防止在动态组件初始化期间触发 onInput? - How to prevent onInput from firing during dynamic component initialization? 如何防止 mouseleave 在 Chrome 中右键单击时触发? - How to prevent mouseleave from firing on right-click in Chrome?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM