动态添加事件监听器


143

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

我有一个组件设置。当单击模板中的某个元素时,我想为mousemove同一模板的另一个元素添加一个侦听器。然后,我想在单击第三个元素时删除此侦听器。

我只是使用普通的Javascript来抓取元素,然后调用标准,addEventListener()但我想知道是否还有更多的“ Angular2.0 ”方式可以做到这一点,我应该研究一下。

Answers:


262

渲染器已在Angular 4.0.0-rc.1中弃用,请阅读以下更新

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,这将使你获得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();
}

这是一个带有示例工作的plnkr。该示例包含的使用listenlistenGlobal

将RendererV2与Angular 4.0.0-rc.1 +一起使用(自4.0.0-rc.3起使用Renderer2

  • 25/02/2017Renderer已弃用,现在我们应该使用RendererV2(请参见下面的行)。参见提交

  • 10/03/2017RendererV2重命名为Renderer2。看到重大变化

RendererV2listenGlobal对全局事件(文档,正文,窗口)不再具有任何功能。它仅具有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

要删除侦听器,它与RendererAngle 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


这只是我使用Angular2的第二天,我几乎还没有开始关注v1,所以很多事情是相当令人困惑的。尽管您给了我很多东西,但我仍在继续阅读,所以我将关闭此书,毫无疑问,我们很快会收到有关很多其他问题的答复。为详细的响应而
喝彩

3
@popClingwrap也可以检查HostListener。在文档中,检查“ 响应用户操作”下的“ 属性”指令,以了解其用法。host
埃里克·马丁内斯

@EricMartinez有没有办法停止监听listen或listenGlobal?(与removeEventListener相同)
Nik

3
@ user1394625是的,你可以在回答看到ngOnDestroy代码,都listenlistenGlobal返回调用时/执行删除侦听器的功能。因此,正如您所看到的那样,我this.func持有的是renderer.listen我返回的函数,当我这样做时,this.func()我将删除监听器。也是一样listenGlobal
埃里克·马丁内斯

@EricMartinez得到了你一个问题...我怎样才能访问“事件”的功能,里面的preventDefault()或stopPropagation()

5

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

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


3
如果要添加侦听器,为什么还要添加该侦听器而返回的函数将调用该侦听器?这对我来说没有多大意义。添加侦听器的全部目的是响应不一定要以编程方式触发的事件。我认为,如果您希望该函数调用您的侦听器,则可能无法完全理解侦听器。
Willwsharp

@tahiche队友,这真的很令人困惑,感谢您指出这一点!
godblessstrawberry

它返回此值,因此稍后在销毁组件时也可以再次删除侦听器。添加侦听器时,最好在以后不再需要它们时将其删除。因此,请存储此返回值并在您的ngOnDestroy方法中调用它。我承认乍一看似乎令人困惑,但这实际上是一个非常有用的功能。自己之后如何清理?
威尔特

1

我将为@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未消毒的同一物体附着在上面,它将证明问题所在。


0

这是我的解决方法:

我使用Angular 6创建了一个库。我添加了一个通用组件commonlib-header,该组件在外部应用程序中像这样使用。

请注意,serviceReference该类是保存方法的类(注入使用的组件constructor(public serviceReference: MyService)commonlib-headerstringFunctionName

<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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.