Aggiungi dinamicamente listener di eventi


143

Sto solo iniziando a scherzare con Angular 2 e mi chiedo se qualcuno può dirmi il modo migliore per aggiungere e rimuovere dinamicamente i listener di eventi dagli elementi.

Ho un componente impostato. Quando si fa clic su un determinato elemento nel modello, voglio aggiungere un ascoltatore mousemovea un altro elemento dello stesso modello. Voglio quindi rimuovere questo ascoltatore quando si fa clic su un terzo elemento.

Ho fatto in modo che funzionasse semplicemente usando Javascript semplice per afferrare gli elementi e quindi chiamare lo standard, addEventListener()ma mi chiedevo se ci fosse un modo più " Angular2.0 " per farlo che dovrei esaminare.

Risposte:


262

Il rendering è stato deprecato in Angular 4.0.0-rc.1, leggi l'aggiornamento di seguito

Il modo angular2 è usare listeno listenGlobalda Renderer

Ad esempio, se si desidera aggiungere un evento clic a un componente, è necessario utilizzare Renderer ed ElementRef (questo offre anche l'opzione di utilizzare ViewChild o qualsiasi cosa che recupera il nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

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

È possibile utilizzare listenGlobalche vi darà l'accesso a document, bodye così via

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

Si noti che da beta.2 entrambi listene che listenGlobalrestituiscono una funzione per rimuovere l'ascoltatore (vedere la sezione relativa alla modifica delle modifiche dal log delle modifiche per beta.2). Questo per evitare perdite di memoria nelle grandi applicazioni (vedi # 6686 ).

Quindi per rimuovere il listener che abbiamo aggiunto dinamicamente dobbiamo assegnare listeno listenGlobala una variabile che conterrà la funzione restituita, e quindi la eseguiremo.

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

Ecco un plnkr con un esempio funzionante. L'esempio contiene l'uso di listene listenGlobal.

Utilizzo di RendererV2 con Angular 4.0.0-rc.1 + (Renderer2 dalla 4.0.0-rc.3)

  • 25/02/2017 : Rendererè stato deprecato, ora dovremmo usare RendererV2(vedi riga sotto). Vedi il commit .

  • 10/03/2017 : è RendererV2stato rinominato in Renderer2. Vedi i cambiamenti di rottura .

RendererV2non ha più listenGlobalfunzione per eventi globali (documento, corpo, finestra). Ha solo una listenfunzione che raggiunge entrambe le funzionalità.

Per riferimento, sto copiando e incollando il codice sorgente dell'implementazione di DOM Renderer poiché potrebbe cambiare (sì, è angolare!).

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

Come puoi vedere, ora verifica se stiamo passando una stringa (documento, corpo o finestra), nel qual caso utilizzerà una addGlobalEventListenerfunzione interna . In ogni altro caso, quando passiamo un elemento (nativeElement) userà un sempliceaddEventListener

Per rimuovere l'ascoltatore è lo stesso di Rendererin angolare 2.x. listenrestituisce una funzione, quindi chiama quella funzione.

Esempio

// 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 con Angular 4.0.0-rc.1 usando RendererV2

plnkr con Angular 4.0.0-rc.3 usando Renderer2


Questo è solo il mio secondo giorno con Angular2 e avevo appena iniziato a girare la testa su v1, quindi molto di questo è piuttosto nuovo e confuso. Mi hai dato un buon carico di cose su cui leggere, quindi sto chiudendo questo e senza dubbio tornerò presto con MOLTE domande più correlate. Saluti per la risposta dettagliata :)
popClingwrap

3
@popClingwrap puoi anche controllare HostListener . Nei documenti, controlla le direttive sugli attributi in Rispondi all'azione dell'utente per vedere come hostviene utilizzato anche.
Eric Martinez,

@EricMartinez c'è un modo per smettere di ascoltare o ascoltare o ascoltare Global? (uguale a removeEventListener)
Nik,

3
@ user1394625 sì, come puoi vedere nella risposta il ngOnDestroycodice, entrambi listene listenGlobalrestituiscono una funzione che quando viene chiamato / eseguito l'ascoltatore viene rimosso. Quindi, come vedi, this.funcmantiene la funzione restituita da renderer.listene quando lo faccio this.func()rimuovo l'ascoltatore. Lo stesso vale per listenGlobal.
Eric Martinez,

@EricMartinez ha ricevuto un'altra domanda per te ... come posso accedere all '"evento" all'interno della funzione per prevenireDefault () o stopPropagation ()
Nik

5

Lo trovo anche estremamente confuso. come @EricMartinez sottolinea Renderer2 hear () restituisce la funzione per rimuovere il listener:

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

Se sto aggiungendo un ascoltatore

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

Mi aspetto che la mia funzione esegua ciò che intendevo, non l'opposto totale che è rimuovere l'ascoltatore.

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

In un determinato scenario, avrebbe effettivamente più senso chiamarlo come:

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

Ci deve essere una buona ragione per questo, ma secondo me è molto fuorviante e non intuitivo.


3
Se stavi aggiungendo un listener, perché dovresti aspettarti che la funzione restituita aggiungendo quel listener invocherà quel listener? Non ha molto senso per me. Il punto centrale dell'aggiunta di un ascoltatore è rispondere a eventi che non si possono necessariamente attivare a livello di programmazione. Penso che se ti aspettavi che quella funzione invocasse il tuo ascoltatore, potresti non capire completamente gli ascoltatori.
Willwsharp,

@tahiche amico, questo è davvero confuso, grazie per averlo sottolineato!
godblessstrawberry,

Restituisce questo in modo da poter anche rimuovere di nuovo l'ascoltatore quando si distrugge il componente in seguito. Quando si aggiungono i listener, è consigliabile rimuoverli in seguito quando non sono più necessari. Quindi memorizza questo valore di ritorno e chiamalo nel tuo ngOnDestroymetodo. Devo ammettere che all'inizio può sembrare confuso, ma in realtà è una funzione molto utile. In quale altro modo ripulire dopo te stesso?
Wilt

1

Aggiungerò un esempio StackBlitz e un commento alla risposta di @tahiche.

Il valore restituito è una funzione per rimuovere il listener di eventi dopo averlo aggiunto. È buona norma rimuovere i listener di eventi quando non sono più necessari. Quindi puoi memorizzare questo valore di ritorno e chiamarlo nel tuo ngOnDestroymetodo.

Ammetto che all'inizio potrebbe sembrare confuso, ma in realtà è una funzione molto utile. In quale altro modo puoi ripulire dopo te stesso?

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

Puoi trovare StackBlitz qui per mostrare come questo potrebbe funzionare per catturare cliccando sugli elementi di ancoraggio.

Ho aggiunto un corpo con un'immagine come segue:
<img src="x" onerror="alert(1)"></div>
per mostrare che il disinfettante sta facendo il suo lavoro.

Qui in questo violino trovi lo stesso corpo attaccato a un innerHTMLsenza sanificarlo e dimostrerà il problema.


0

Ecco la mia soluzione alternativa:

Ho creato una libreria con Angular 6. Ho aggiunto un componente comune commonlib-headerche viene utilizzato in questo modo in un'applicazione esterna.

Nota serviceReferencequale è la classe (iniettata nel componente constructor(public serviceReference: MyService)che utilizza il commonlib-header) che contiene il stringFunctionNamemetodo:

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

Il componente della libreria è programmato in questo modo. L'evento dinamico viene aggiunto nel onClick(fn: any)metodo:

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

Riutilizzabile 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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.