イベントリスナーを動的に追加する


143

私はAngular 2をいじくり始めたばかりで、イベントリスナーを要素に動的に追加および削除する最良の方法を誰かが教えてくれるかどうか疑問に思っています。

コンポーネントをセットアップしました。テンプレート内の特定の要素をクリックしたときmousemoveに、同じテンプレートの別の要素にリスナーを追加したいと思います。次に、3番目の要素がクリックされたときにこのリスナーを削除します。

単純なJavascriptを使用して要素を取得し、標準を呼び出すだけでこれが機能するようになりaddEventListener()ましたが、これを行うには、 " Angular2.0 "の方法がもっとあるのではないかと思いました。

回答:


262

レンダラーはAngular 4.0.0-rc.1で非推奨になりました。以下の更新をお読みください

angular2方法が使用されlistenたりlistenGlobalからレンダラ

たとえば、コンポーネントにクリックイベントを追加する場合は、レンダラーと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を参照)。

したがって、動的に追加したリスナーを削除するには、返される関数を保持する変数に、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

Angular 4.0.0-rc.1 +でのRendererV2の使用(4.0.0-rc.3以降のRenderer2)

  • 25/02/2017Renderer廃止されました。今は使用する必要がありますRendererV2(以下の行を参照)。コミットをご覧ください。

  • 2017年10月3日RendererV2に名前が変更されましたRenderer2重大な変更を参照してください。

RendererV2listenGlobalグローバルイベント(ドキュメント、ボディ、ウィンドウ)に対する機能がなくなりました。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();

plnkr角度4.0.0-rc.1使用RendererV2を

plnkr角度4.0.0-rc.3使用Renderer2を


今日はAngular2の2日目であり、v1について頭を悩ませ始めていたので、この多くはかなり新しい混乱を招いています。読み終えるためにたくさんの情報を提供してくれたので、私はこれを締めくくり、さらに多くの関連する質問で間違いなくすぐに戻ってきます。詳細な応答への乾杯:)
popClingwrap

3
@popClingwrapでは、HostListenerも確認できます。ドキュメントで、Respond to user actionの下のAttributeディレクティブをチェックして、の使用方法も確認してください。host
Eric Martinez

@EricMartinezは、listenまたはlistenGlobalのいずれかのリスニングを停止する方法はありますか?(removeEventListenerと同じ)
Nik

3
@ user1394625はい、あなたは答えに見ることができるようにngOnDestroyコードを、両方listenlistenGlobal呼ばれる/実行リスナーが削除されていることの機能を返します。だから、あなたが見るようthis.funcで返される機能を保持しているrenderer.listenと私はときthis.func()、私は、リスナーを削除しています。同じことがについても言えlistenGlobalます。
Eric Martinez

@EricMartinezがもう1つ質問をしました... preventDefault()またはstopPropagation()の関数内の「イベント」にアクセスするにはどうすればよいですか
Nik

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

@tahiche mateこれは本当に混乱しています、これを指摘してくれてありがとう!
godblessstrawberry 2018

これはこれを返すので、後でコンポーネントを破棄するときにリスナーを再び削除することもできます。リスナーを追加するときは、不要になったリスナーを後で削除することをお勧めします。したがって、この戻り値を保存して、ngOnDestroyメソッド内で呼び出します。最初はわかりにくいかもしれませんが、実際には非常に便利な機能です。他にどのように自分の後を片付けますか?
ウィルト

1

私が追加されますStackBlitz例と@tahicheからの回答にコメントを。

戻り値は、追加したイベントリスナーを削除する関数です。イベントリスナーが不要になった場合は、削除することをお勧めします。したがって、この戻り値を保存して、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-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>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.