Tambahkan pendengar acara secara dinamis

143

Saya baru saja mulai bermain-main dengan Angular 2 dan saya ingin tahu apakah ada yang bisa memberi tahu saya cara terbaik untuk secara dinamis menambah dan menghapus pendengar acara dari elemen.

Saya sudah menyiapkan komponen. Ketika elemen tertentu dalam template diklik saya ingin menambahkan pendengar untuk mousemoveelemen lain dari template yang sama. Saya kemudian ingin menghapus pendengar ini ketika elemen ketiga diklik.

Saya agak mendapatkan ini berfungsi hanya menggunakan Javascript biasa untuk mengambil elemen dan kemudian memanggil standar addEventListener()tapi saya bertanya-tanya apakah ada cara yang lebih " Angular2.0 " untuk melakukan ini yang harus saya cari.

popClingwrap
sumber

Jawaban:

262

Renderer telah ditinggalkan dalam Angular 4.0.0-rc.1, baca pembaruan di bawah ini

Cara angular2 adalah menggunakan listenatau listenGlobaldari Renderer

Misalnya, jika Anda ingin menambahkan acara klik ke Komponen, Anda harus menggunakan Renderer dan ElementRef (ini memberi Anda juga opsi untuk menggunakan ViewChild, atau apa pun yang mengambil nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

    // Listen to click events in the component
    renderer.listen(elementRef.nativeElement, 'click', (event) => {
      // Do something with 'event'
    })
);

Anda dapat menggunakan listenGlobalyang akan memberikan Anda akses ke document, body, dll

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

Perhatikan bahwa sejak beta.2 keduanya listendan listenGlobalmengembalikan fungsi untuk menghapus pendengar (lihat memecah bagian perubahan dari changelog untuk beta.2). Ini untuk menghindari kebocoran memori pada aplikasi besar (lihat # 6686 ).

Jadi untuk menghapus pendengar kita menambahkan secara dinamis kita harus menetapkan listenatau listenGlobalke variabel yang akan menahan fungsi dikembalikan, dan kemudian kita jalankan.

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

Berikut ini adalah plnkr dengan contoh yang berfungsi. Contoh tersebut berisi penggunaan listendan listenGlobal.

Menggunakan RendererV2 dengan Angular 4.0.0-rc.1 + (Renderer2 sejak 4.0.0-rc.3)

  • 25/02/2017 : Renderertelah ditinggalkan, sekarang kita harus menggunakan RendererV2(lihat baris di bawah). Lihat komit .

  • 10/03/2017 : RendererV2diubah namanya menjadi Renderer2. Lihat perubahan yang melanggar .

RendererV2tidak memiliki listenGlobalfungsi lagi untuk acara global (dokumen, badan, jendela). Ini hanya memiliki listenfungsi yang mencapai kedua fungsi.

Untuk referensi, saya menyalin & menempelkan kode sumber implementasi DOM Renderer karena mungkin berubah (ya, itu sudut!).

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

Seperti yang Anda lihat, sekarang memverifikasi jika kita melewati sebuah string (dokumen, badan atau jendela), dalam hal ini akan menggunakan addGlobalEventListenerfungsi internal . Dalam kasus lain, ketika kita melewati elemen (nativeElement) itu akan menggunakan yang sederhanaaddEventListener

Untuk menghapus pendengar itu sama dengan Rendererdi sudut 2.x. listenmengembalikan fungsi, lalu memanggil fungsi itu.

Contoh

// 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 dengan Angular 4.0.0-rc.1 menggunakan RendererV2

plnkr dengan Angular 4.0.0-rc.3 menggunakan Renderer2

Eric Martinez
sumber
Ini hanya hari kedua saya dengan Angular2 dan saya baru saja mulai mendapatkan kepalaku sekitar v1 sehingga banyak dari ini agak baru membingungkan. Anda telah memberi saya banyak hal untuk dibaca, jadi saya akan menutup yang satu ini dan pasti akan segera kembali dengan BANYAK pertanyaan terkait lainnya. Ceria untuk tanggapan terperinci :)
popClingwrap
3
@popClingwrap Anda dapat memeriksa HostListener juga. Dalam dokumen, periksa arahan Atribut di bawah Tanggapi tindakan pengguna untuk melihat bagaimana hostdigunakan juga.
Eric Martinez
@EricMartinez apakah ada cara untuk berhenti mendengarkan baik mendengarkan atau mendengarkan Global? (sama seperti removeEventListener)
Nik
3
@ user1394625 ya, seperti yang Anda lihat di jawaban ngOnDestroykode, keduanya listendan listenGlobalmengembalikan fungsi yang ketika dipanggil / dieksekusi pendengar dihapus. Jadi seperti yang Anda lihat this.funcadalah menahan fungsi yang dikembalikan oleh renderer.listendan ketika saya melakukannya this.func()saya menghapus pendengar. Hal yang sama berlaku untuk listenGlobal.
Eric Martinez
@EricMartinez punya satu pertanyaan lagi untuk Anda ... bagaimana saya bisa mengakses 'acara' di dalam fungsi untuk mencegahDefault () atau stopPropagation ()
Nik
5

Saya juga menemukan ini sangat membingungkan. sebagai @EricMartinez menunjukkan Renderer2 listen () mengembalikan fungsi untuk menghapus pendengar:

ƒ () { return element.removeEventListener(eventName, /** @type {?} */ (handler), false); }

Jika saya menambahkan pendengar

this.listenToClick = this.renderer.listen('document', 'click', (evt) => {
    alert('Clicking the document');
})

Saya mengharapkan fungsi saya untuk mengeksekusi apa yang saya inginkan, bukan kebalikan total yang menghapus pendengar.

// I´d expect an alert('Clicking the document'); 
this.listenToClick();
// what you actually get is removing the listener, so nothing...

Dalam skenario yang diberikan, sebenarnya lebih masuk akal untuk memberi nama seperti:

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

Pasti ada alasan bagus untuk ini, tetapi menurut saya itu sangat menyesatkan dan tidak intuitif.

tahiche
sumber
3
Jika Anda menambahkan pendengar, mengapa Anda berharap bahwa fungsi dikembalikan dengan menambahkan pendengar itu akan memanggil pendengar itu? Itu tidak masuk akal bagi saya. Inti dari menambahkan pendengar adalah untuk merespons acara yang tidak dapat Anda memicu secara terprogram. Saya pikir jika Anda mengharapkan fungsi itu memanggil pendengar Anda, Anda mungkin tidak sepenuhnya memahami pendengar.
Willwsharp
@tahiche sobat ini benar-benar membingungkan, terima kasih telah menunjukkan ini!
godblessstrawberry
Ini mengembalikan ini sehingga Anda juga dapat menghapus pendengar lagi ketika Anda menghancurkan komponen Anda nanti. Saat menambahkan pendengar itu dianggap praktik yang baik untuk menghapusnya nanti ketika Anda tidak membutuhkannya lagi. Jadi simpan nilai pengembalian ini dan sebut di dalam ngOnDestroymetode Anda . Saya akui bahwa itu mungkin tampak membingungkan pada awalnya, tetapi sebenarnya ini adalah fitur yang sangat berguna. Bagaimana lagi membersihkan setelah dirimu sendiri?
Layu
1

Saya akan menambahkan contoh StackBlitz dan komentar untuk jawaban dari @tahiche.

Nilai kembali adalah fungsi untuk menghapus pendengar acara setelah Anda menambahkannya. Ini dianggap praktik yang baik untuk menghapus pendengar acara ketika Anda tidak membutuhkannya lagi. Jadi, Anda dapat menyimpan nilai pengembalian ini dan menyebutnya di dalam ngOnDestroymetode Anda .

Saya akui bahwa itu mungkin tampak membingungkan pada awalnya, tetapi sebenarnya ini adalah fitur yang sangat berguna. Bagaimana lagi Anda bisa membersihkan diri sendiri?

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

Anda dapat menemukan StackBlitz di sini untuk menunjukkan bagaimana ini bisa bekerja untuk menangkap klik pada elemen jangkar.

Saya menambahkan tubuh dengan gambar sebagai berikut:
<img src="x" onerror="alert(1)"></div>
untuk menunjukkan bahwa pembersih sedang melakukan tugasnya.

Di sini, di biola ini Anda menemukan tubuh yang sama melekat pada innerHTMLtanpa membersihkannya dan itu akan menunjukkan masalah.

Melayu
sumber
0

Inilah solusi saya:

Saya membuat perpustakaan dengan Angular 6. Saya menambahkan komponen umum commonlib-headeryang digunakan seperti ini dalam aplikasi eksternal.

Perhatikan serviceReferenceyang merupakan kelas (disuntikkan dalam komponen constructor(public serviceReference: MyService)yang menggunakan commonlib-header) yang menampung stringFunctionNamemetode:

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

Komponen perpustakaan diprogram seperti ini. Acara dinamis ditambahkan dalam onClick(fn: any)metode:

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

Dapat digunakan kembali 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>
Gus
sumber