Dinamik olarak olay dinleyicisi ekle


143

Sadece Açısal 2 ile uğraşmaya başladım ve merak ediyorum, biri bana olay dinleyicilerini öğelere dinamik olarak eklemek ve kaldırmak için en iyi yolu söyleyebilir mi?

Bir bileşen ayarladım. Şablondaki belirli bir öğe tıklatıldığında mousemove, aynı şablonun başka bir öğesine bir dinleyici eklemek istiyorum . Sonra üçüncü bir öğe tıklatıldığında bu dinleyiciyi kaldırmak istiyorum.

Bu tür sadece öğeleri kapmak ve sonra standart çağırmak için düz Javascript kullanarak bu çalışma var addEventListener()ama içine bakmak gerekir bunu yapmanın daha " Angular2.0 " yolu olup olmadığını merak ettim .

Yanıtlar:


262

Oluşturucu, Açısal 4.0.0-rc.1'de kullanımdan kaldırıldı, aşağıdaki güncellemeyi okuyun

Angular2 yolu kullanımına olan listenveya listenGlobalgelen Oluşturucu

Eğer bir bileşen için bir tıklama etkinlik eklemek istiyorsanız Örneğin, kullanmak zorunda Oluşturucu ve ElementRef (bu yanı alır ViewChild kullanma seçeneğini, ya da bir şey verir nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

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

Sen kullanabilirsiniz listenGlobalsize erişim vereceğini document, bodyvb

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

Beta.2 beri de o Not listenve listenGlobal(bakınız dinleyici kaldırmak için bir fonksiyon geri değişiklikler kırılma beta.2 için değişiklikler ile ilgili bölümü). Bu, büyük uygulamalarda bellek sızıntılarını önlemek içindir (bkz. # 6686 ).

Dinamik olarak eklediğimiz dinleyiciyi kaldırmak için , döndürülen işlevi tutacak bir değişken atamalı listenveya listenGlobalvermeliyiz ve sonra çalıştırıyoruz.

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

İşte bir örnek çalışan bir plnkr . Örnek kullanımını içerir listenve listenGlobal.

Açısal 4.0.0-rc.1 + ile RendererV2 kullanma (4.0.0-rc.3'ten beri Renderer2)

  • 25/02/2017 : Rendererkullanımdan kaldırıldı, şimdi kullanmalıyız RendererV2(aşağıdaki satıra bakın). Taahhüdüne bakın .

  • 10/03/2017 : RendererV2olarak yeniden adlandırıldı Renderer2. Değişikliklere bakınız .

RendererV2artık listenGlobalglobal etkinlikler (belge, gövde, pencere) için bir işlevi yoktur . Sadece listenher iki işlevi de yerine getiren bir işlevi vardır.

Referans olarak, DOM Renderer uygulamasının kaynak kodunu değiştirebildiğimden beri kopyalayıp yapıştırıyorum (evet, açısal!).

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

Gördüğünüz gibi, şimdi bir dize (belge, gövde veya pencere) geçirip geçirmediğimizi doğrular, bu durumda dahili bir addGlobalEventListenerişlev kullanır. Başka bir durumda, bir öğeyi (nativeElement) geçirdiğimizde, basit biraddEventListener

Dinleyiciyi kaldırmak için Rendereraçısal 2.x ile aynıdır . listenbir işlev döndürür, sonra o işlevi çağırır.

Misal

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

RendererV2 kullanarak Angular 4.0.0- rc.1 ile plnkr

Renderer2 kullanarak Angular 4.0.0- rc.3 ile plnkr


Bu sadece Angular2 ile ikinci günüm ve başımı v1 etrafında zorlukla almaya başladım, bu yüzden çoğu oldukça kafa karıştırıcı. Bana okumak için iyi bir sürü şey verdin, bu yüzden bu bir kapatıyorum ve şüphesiz LOTS daha fazla ilgili soru ile yakında olacak. Ayrıntılı tepki için şerefe :)
popClingwrap

3
Kontrol edebileceğiniz @popClingwrap HostListener sıra. Dokümanlarda, nasıl kullanıldığını görmek için Kullanıcıya yanıtla eylemi altındaki Öznitelik direktiflerini kontrol edin . host
Eric Martinez

@EricMartinez dinlemek ya da dinlemek için bir yol var mı? (removeEventListener ile aynı)
Nik

3
@ user1394625 evet, cevap gördüğünüz gibi ngOnDestroykod, hem listenve listenGlobalçağrıldığında / kaldırılır dinleyici yürütülen bir işlev döndürecek. Gördüğünüz gibi this.funcdöndürülen işlevi tutuyor renderer.listenve yaptığımda this.func()dinleyiciyi kaldırıyorum. Aynı şey geçerli listenGlobal.
Eric Martinez

@EricMartinez sizin için bir soru daha aldı ... önlem için fonksiyon içindeki 'olaya' nasıl erişebilirimDefault () veya stopPropagation ()
Nik

5

Ben de bunu son derece kafa karıştırıcı buluyorum. @EricMartinez'in işaret ettiği gibi Renderer2 listen () dinleyiciyi kaldırma işlevini döndürür:

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

Bir dinleyici ekliyorsam

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

Ben fonksiyonum dinleyiciyi kaldırmak olan tam tersi değil, ne istediğimi yürütmek için bekliyoruz.

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

Verilen senaryoda, aslında bu şekilde adlandırmak daha anlamlı olur:

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

Bunun için iyi bir neden olmalı ama bence çok yanıltıcı ve sezgisel değil.


3
Bir dinleyici ekliyorsanız, neden bu dinleyiciyi ekleyerek döndürülen işlevin bu dinleyiciyi çağırmasını beklersiniz? Bu bana pek mantıklı gelmiyor. Bir dinleyici eklemenin amacı, programlı olarak tetikleyemeyeceğiniz olaylara yanıt vermektir. Bu işlevin dinleyicinizi çağırmasını beklediyseniz, dinleyicileri tam olarak anlayamayabilirsiniz.
Willwsharp

@tahiche dostum bu gerçekten kafa karıştırıcı, bunu işaret ettiğiniz için teşekkürler!
godblessstrawberry

Bunu döndürür, böylece daha sonra bileşeninizi yok ettiğinizde dinleyiciyi tekrar kaldırabilirsiniz. Dinleyici eklerken, artık ihtiyacınız olmadığında bunları kaldırmak daha iyi bir uygulama olarak kabul edilir. Bu nedenle bu dönüş değerini saklayın ve ngOnDestroyyönteminizin içinde çağırın . İlk başta kafa karıştırıcı göründüğünü itiraf ediyorum, ama aslında çok kullanışlı bir özellik. Kendinizden sonra başka nasıl temizlenir?
Wilt

1

@Tahiche yanıtına bir StackBlitz örneği ve bir yorum ekleyeceğim.

Dönüş değeri, olay dinleyicisini ekledikten sonra kaldırmak için kullanılan bir işlevdir. Olay dinleyicilerini artık ihtiyacınız olmadığında kaldırmak iyi bir uygulama olarak kabul edilir. Böylece bu dönüş değerini saklayabilir ve ngOnDestroyyönteminizin içinde çağırabilirsiniz .

İlk başta kafa karıştırıcı göründüğünü itiraf ediyorum, ama aslında çok kullanışlı bir özellik. Kendinizden sonra başka nasıl temizleyebilirsiniz?

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

Bunun bir sabitleme öğelerini tıklatmak için nasıl çalışabileceğini göstermek için bir StackBlitz bulabilirsiniz .

Aşağıdaki gibi bir görüntüye sahip bir gövde ekledim:
<img src="x" onerror="alert(1)"></div>
dezenfektanın işini yaptığını göstermek için.

Burada bu kemanda aynı vücudu innerHTMLsanitize etmeden bağlı bulursunuz ve sorunu gösterecektir.


0

İşte benim geçici çözüm:

Angular 6 ile bir kütüphane oluşturdum. Ortak bir bileşen ekledim commonlib-header . Harici bir uygulamada bu şekilde kullanılan .

Not serviceReference(bileşen enjekte sınıfı olan constructor(public serviceReference: MyService)kullanır commonlib-headertutan) stringFunctionNameyöntemi:

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

Kütüphane bileşeni bu şekilde programlanır. Dinamik olay onClick(fn: any)yönteme eklenir :

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

Yeniden kullanılabilir 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>
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.