簡體   English   中英

動態添加事件監聽器

[英]Dynamically add event listener

我剛剛開始使用 Angular 2,我想知道是否有人可以告訴我從元素中動態添加和刪除事件偵聽器的最佳方法。

我有一個組件設置。 單擊模板中的某個元素時,我想將mousemove的偵聽器添加到同一模板的另一個元素。 然后我想在單擊第三個元素時刪除此偵聽器。

我只是使用普通的 Javascript 來獲取元素,然后調用標准的addEventListener()但我想知道是否有更多的“ Angular2.0 ”方式來做這件事,我應該研究一下。

渲染器已在 Angular 4.0.0-rc.1 中棄用,請閱讀下面的更新

angular2 的方式是使用來自Renderer 的listenlistenGlobal

例如,如果您想向組件添加單擊事件,則必須使用 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的用法。

在 Angular 4.0.0-rc.1+ 中使用 RendererV2 (Renderer2 自 4.0.0-rc.3)

  • 25/02/2017Renderer已被棄用,現在我們應該使用RendererV2 (見下行)。 請參閱提交

  • 10/03/2017RendererV2更名為Renderer2 查看重大變化

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 ,它與Renderer 2.x 中的Renderer相同。 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();

plnkrAngular 4.0.0-rc.1使用RendererV2

plnkrAngular 4.0.0-rc.3使用Renderer2

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

這一定有一個很好的理由,但在我看來,這是非常具有誤導性且不直觀的。

我將在@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是包含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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM