动态添加事件侦听器

我刚刚开始使用 Angular 2,我想知道是否有人可以告诉我从元素中动态添加和删除事件侦听器的最佳方法。

我已经建立了一个组件。当单击模板中的某个元素时,我希望将 mousemove的侦听器添加到同一模板的另一个元素中。然后,当单击第三个元素时,我想删除这个侦听器。

我只是使用普通的 Javascript 来获取元素,然后调用标准的 addEventListener(),但是我想知道是否有一种更“ Angular2.0”的方法来做这件事,我应该研究一下。

189746 次浏览

渲染器在角度4.0.0-rc 1中已被弃用,请阅读下面的更新

有棱角的方向是从 渲染器中使用 listenlistenGlobal

例如,如果你想向一个组件添加一个 click 事件,你必须使用 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来访问 documentbody等。

renderer.listenGlobal('document', 'click', (event) => {
// Do something with 'event'
});

注意,由于 beta.2,listenlistenGlobal都返回一个函数来删除侦听器(请参阅更改日志中的 突破性的改变部分以获得 beta.2)。这是为了避免大型应用程序中的内存泄漏(请参见 # 6686)。

因此,为了删除动态添加的侦听器,我们必须将 listenlistenGlobal赋给一个变量,该变量将保存返回的函数,然后执行它。

// 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();
}

下面是一个 普林克的例子,其中包含了 listenlistenGlobal的用法。

使用 RendererV2和 Angular 4.0.0-rc. 1 + < sub > < em > (Renderer2 since 4.0.0-rc. 3)

  • 25/02/2017 : Renderer已被废弃,现在我们应该使用 RendererV2(见下一行)。

  • 10/03/2017 : RendererV2被重命名为 Renderer2

对于全局事件(文档、主体、窗口) ,RendererV2 没有更多的 listenGlobal函数。它只有一个 listen功能,实现了这两个功能。

作为参考,我正在复制和粘贴的 源代码的 DOM 渲染器实现,因为它可能会改变(是的,它的角!)。

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在角度为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();

使用 < strong > RendererV2与 角度4.0.0-rc 1一起使用 plnkr

使用 < strong > Renderer2与 角度4.0.0-rc 3一起使用 plnkr

我也觉得这非常令人困惑。 As@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);
});

这一定有一个很好的理由,但在我看来,这是非常误导和不直观的。

我的解决办法是:

我用角度6创建了一个图书馆。我添加了一个公共组件 commonlib-header,它在外部应用程序中像这样使用。

注意,serviceReference是保存 stringFunctionName方法的类(注入到使用 commonlib-header的组件 constructor(public serviceReference: MyService)中) :

<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>

我将为@tahiche 的回答添加一个 一个 href = “ https://StackBlitz.com/edit/angle-dom-sanitizer-rdaguv? file = src/app/app.Component. ts”rel = “ norefrer”> StackBlitz example 和一条注释。

返回值是一个函数,用于在添加事件侦听器后删除它。当您不再需要事件侦听器时,删除它们被认为是一种很好的做法。因此可以存储这个返回值并在 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没有消毒它,它将证明这个问题。