我刚刚开始使用Angular 2,我想知道是否有人可以告诉我从元素动态添加和删除事件侦听器的最佳方法。
我有一个组件设置。当单击模板中的某个元素时,我想为mousemove
同一模板的另一个元素添加一个侦听器。然后,我想在单击第三个元素时删除此侦听器。
我只是使用普通的Javascript来抓取元素,然后调用标准,addEventListener()
但我想知道是否还有更多的“ Angular2.0 ”方式可以做到这一点,我应该研究一下。
我刚刚开始使用Angular 2,我想知道是否有人可以告诉我从元素动态添加和删除事件侦听器的最佳方法。
我有一个组件设置。当单击模板中的某个元素时,我想为mousemove
同一模板的另一个元素添加一个侦听器。然后,我想在单击第三个元素时删除此侦听器。
我只是使用普通的Javascript来抓取元素,然后调用标准,addEventListener()
但我想知道是否还有更多的“ Angular2.0 ”方式可以做到这一点,我应该研究一下。
Answers:
该angular2方式是使用listen
还是listenGlobal
从渲染器
例如,如果您要将click事件添加到Component,则必须使用Renderer和ElementRef(这也使您可以选择使用ViewChild或检索的任何东西nativeElement
)
constructor(elementRef: ElementRef, renderer: Renderer) {
// Listen to click events in the component
renderer.listen(elementRef.nativeElement, 'click', (event) => {
// Do something with 'event'
})
);
您可以使用listenGlobal
,这将使你获得document
,body
等等。
renderer.listenGlobal('document', 'click', (event) => {
// Do something with 'event'
});
注意,由于beta.2都listen
和listenGlobal
返回功能删除侦听器(参见重大更改部分从更新日志的beta.2)。这是为了避免大型应用程序中的内存泄漏(请参阅#6686)。
因此,要删除我们动态添加的侦听器,我们必须将赋值listen
或listenGlobal
给将保存返回函数的变量赋值,然后执行它。
// listenFunc will hold the function returned by "renderer.listen"
listenFunc: Function;
// globalListenFunc will hold the function returned by "renderer.listenGlobal"
globalListenFunc: Function;
constructor(elementRef: ElementRef, renderer: Renderer) {
// We cache the function "listen" returns
this.listenFunc = renderer.listen(elementRef.nativeElement, 'click', (event) => {
// Do something with 'event'
});
// We cache the function "listenGlobal" returns
this.globalListenFunc = renderer.listenGlobal('document', 'click', (event) => {
// Do something with 'event'
});
}
ngOnDestroy() {
// We execute both functions to remove the respectives listeners
// Removes "listen" listener
this.listenFunc();
// Removs "listenGlobal" listener
this.globalListenFunc();
}
这是一个带有示例工作的plnkr。该示例包含的使用listen
和listenGlobal
。
RendererV2
listenGlobal
对全局事件(文档,正文,窗口)不再具有任何功能。它仅具有listen
同时实现这两种功能的功能。
作为参考,我将复制并粘贴DOM Renderer实现的源代码,因为它可能会更改(是的,它是有角度的!)。
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
() => void {
if (typeof target === 'string') {
return <() => void>this.eventManager.addGlobalEventListener(
target, event, decoratePreventDefault(callback));
}
return <() => void>this.eventManager.addEventListener(
target, event, decoratePreventDefault(callback)) as() => void;
}
如您所见,现在它将验证我们是否要传递字符串(文档,正文或窗口),在这种情况下它将使用内部addGlobalEventListener
函数。在任何其他情况下,当我们传递元素(nativeElement)时,它将使用一个简单的addEventListener
要删除侦听器,它与Renderer
Angle 2.x 中的侦听器相同。listen
返回一个函数,然后调用该函数。
// Add listeners
let global = this.renderer.listen('document', 'click', (evt) => {
console.log('Clicking the document', evt);
})
let simple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
console.log('Clicking the button', evt);
});
// Remove listeners
global();
simple();
plnkr与角4.0.0-RC.1使用RendererV2
plnkr与角4.0.0-rc.3使用Renderer2
ngOnDestroy
代码,都listen
和listenGlobal
返回调用时/执行删除侦听器的功能。因此,正如您所看到的那样,我this.func
持有的是renderer.listen
我返回的函数,当我这样做时,this.func()
我将删除监听器。也是一样listenGlobal
。
我也觉得这非常令人困惑。正如@EricMartinez指出的那样,Renderer2 listen()返回删除监听器的函数:
ƒ () { return element.removeEventListener(eventName, /** @type {?} */ (handler), false); }
如果我要添加一个侦听器
this.listenToClick = this.renderer.listen('document', 'click', (evt) => {
alert('Clicking the document');
})
我希望我的功能能够执行我想要的功能,而不是删除监听器的相反功能。
// I´d expect an alert('Clicking the document');
this.listenToClick();
// what you actually get is removing the listener, so nothing...
在给定的场景中,将其命名为实际上更有意义:
// Add listeners
let unlistenGlobal = this.renderer.listen('document', 'click', (evt) => {
console.log('Clicking the document', evt);
})
let removeSimple = this.renderer.listen(this.myButton.nativeElement, 'click', (evt) => {
console.log('Clicking the button', evt);
});
一定有一个很好的理由,但是在我看来,这是非常误导且不直观的。
ngOnDestroy
方法中调用它。我承认乍一看似乎令人困惑,但这实际上是一个非常有用的功能。自己之后如何清理?
我将为@tahiche的答案添加一个StackBlitz示例和注释。
返回值是在添加事件侦听器后将其删除的函数。在不再需要事件侦听器时,将其视为一种好习惯。因此,您可以存储此返回值并在您的内部调用它ngOnDestroy
方法中。
我承认乍一看似乎令人困惑,但这实际上是一个非常有用的功能。你自己之后还能如何清理?
export class MyComponent implements OnInit, OnDestroy {
public removeEventListener: () => void;
constructor(
private renderer: Renderer2,
private elementRef: ElementRef
) {
}
public ngOnInit() {
this.removeEventListener = this.renderer.listen(this.elementRef.nativeElement, 'click', (event) => {
if (event.target instanceof HTMLAnchorElement) {
// Prevent opening anchors the default way
event.preventDefault();
// Your custom anchor click event handler
this.handleAnchorClick(event);
}
});
}
public ngOnDestroy() {
this.removeEventListener();
}
}
您可以在此处找到StackBlitz,以显示如何在捕获锚元素上单击。
我添加了带有图像的主体,如下所示:
<img src="x" onerror="alert(1)"></div>
以显示消毒剂正在执行其工作。
在此小提琴中,您发现innerHTML
未消毒的同一物体附着在上面,它将证明问题所在。
这是我的解决方法:
我使用Angular 6创建了一个库。我添加了一个通用组件commonlib-header
,该组件在外部应用程序中像这样使用。
请注意,serviceReference
该类是保存方法的类(注入使用的组件constructor(public serviceReference: MyService)
中commonlib-header
)stringFunctionName
:
<commonlib-header
[logo]="{ src: 'assets/img/logo.svg', alt: 'Logo', href: '#' }"
[buttons]="[{ index: 0, innerHtml: 'Button', class: 'btn btn-primary', onClick: [serviceReference, 'stringFunctionName', ['arg1','arg2','arg3']] }]">
</common-header>
库组件是这样编程的。动态事件将添加到onClick(fn: any)
方法中:
export class HeaderComponent implements OnInit {
_buttons: Array<NavItem> = []
@Input()
set buttons(buttons: Array<any>) {
buttons.forEach(navItem => {
let _navItem = new NavItem(navItem.href, navItem.innerHtml)
_navItem.class = navItem.class
_navItem.onClick = navItem.onClick // this is the array from the component @Input properties above
this._buttons[navItem.index] = _navItem
})
}
constructor() {}
ngOnInit() {}
onClick(fn: any){
let ref = fn[0]
let fnName = fn[1]
let args = fn[2]
ref[fnName].apply(ref, args)
}
可重用的header.component.html
:
<div class="topbar-right">
<button *ngFor="let btn of _buttons"
class="{{ btn.class }}"
(click)="onClick(btn.onClick)"
[innerHTML]="btn.innerHtml | keepHtml"></button>
</div>