[英]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: 您可以执行以下操作:
click
event of the component, and emit this event when the button is clicked click
事件,并在单击按钮时发出此事件 pointer-events: none
on the component host pointer-events: none
pointer-events: auto
on the button pointer-events: auto
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
。)
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.