Ereignis-Listener dynamisch hinzufügen

143

Ich fange gerade an, mit Angular 2 herumzuspielen, und ich frage mich, ob mir jemand sagen kann, wie ich Ereignis-Listener am besten dynamisch zu Elementen hinzufügen und daraus entfernen kann.

Ich habe eine Komponente eingerichtet. Wenn auf ein bestimmtes Element in der Vorlage geklickt wird, möchte ich einem mousemoveanderen Element derselben Vorlage einen Listener hinzufügen . Ich möchte diesen Listener dann entfernen, wenn auf ein drittes Element geklickt wird.

Ich habe das irgendwie zum Laufen gebracht , indem ich einfach Javascript verwendet habe, um die Elemente zu erfassen und dann den Standard aufzurufen, addEventListener()aber ich habe mich gefragt, ob es eine " Angular2.0 " -Methode gibt , die ich untersuchen sollte.

popClingwrap
quelle

Antworten:

262

Der Renderer ist in Angular 4.0.0-rc.1 veraltet. Lesen Sie das folgende Update

Der Angular2-Weg ist die Verwendung von listenoder listenGlobalvon Renderer

Wenn Sie beispielsweise einer Komponente ein Klickereignis hinzufügen möchten, müssen Sie Renderer und ElementRef verwenden (dies gibt Ihnen auch die Möglichkeit, ViewChild oder alles zu verwenden, was das abruft nativeElement).

constructor(elementRef: ElementRef, renderer: Renderer) {

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

Sie können verwendet werden, listenGlobaldass Sie den Zugang zu document, bodyusw.

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

Beachten Sie, dass seit Beta.2 beide listenund listenGlobaleine Funktion zum Entfernen des Listeners zurückgegeben werden (siehe Abschnitt zum Brechen von Änderungen aus dem Änderungsprotokoll für Beta.2). Dies dient dazu, Speicherlecks in großen Anwendungen zu vermeiden (siehe Nr. 6686 ).

Um den Listener zu entfernen, den wir dynamisch hinzugefügt haben, müssen wir listenoder listenGlobaleine Variable zuweisen , die die zurückgegebene Funktion enthält, und dann führen wir sie aus.

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

Hier ist ein Beispiel mit einem Beispiel. Das Beispiel enthält die Verwendung von listenund listenGlobal.

Verwenden von RendererV2 mit Angular 4.0.0-rc.1 + (Renderer2 seit 4.0.0-rc.3)

  • 25/02/2017 : Rendererwurde veraltet, jetzt sollten wir verwenden RendererV2(siehe Zeile unten). Siehe das Commit .

  • 10/03/2017 : RendererV2wurde umbenannt in Renderer2. Siehe die wichtigsten Änderungen .

RendererV2hat keine listenGlobalFunktion mehr für globale Ereignisse (Dokument, Text, Fenster). Es hat nur eine listenFunktion, die beide Funktionen erfüllt.

Als Referenz kopiere und füge ich den Quellcode der DOM Renderer-Implementierung ein, da er sich möglicherweise ändert (ja, er ist eckig!).

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

Wie Sie sehen können, wird jetzt überprüft, ob eine Zeichenfolge (Dokument, Text oder Fenster) übergeben wird. In diesem Fall wird eine interne addGlobalEventListenerFunktion verwendet. In jedem anderen Fall wird beim Übergeben eines Elements (nativeElement) ein einfaches verwendetaddEventListener

Um den Listener zu entfernen, ist es dasselbe wie Rendererin Angular 2.x. listenGibt eine Funktion zurück und ruft diese Funktion auf.

Beispiel

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

plnkr mit Angular 4.0.0-rc.3 unter Verwendung von Renderer2

Eric Martinez
quelle
Dies ist erst mein zweiter Tag mit Angular2 und ich hatte kaum angefangen, mich mit v1 zu beschäftigen, daher ist vieles ziemlich verwirrend. Sie haben mir eine Menge Dinge zum Lesen gegeben, also schließe ich diese ab und werde zweifellos bald mit VIELEN weiteren Fragen zurück sein. Prost auf die ausführliche Antwort :)
PopClingwrap
3
@popClingwrap können Sie auch HostListener überprüfen . Überprüfen Sie in den Dokumenten die Attributanweisungen unter Auf Benutzeraktion reagieren, um zu sehen, wie hostsie ebenfalls verwendet werden.
Eric Martinez
@EricMartinez gibt es eine Möglichkeit, nicht mehr zuzuhören oder zu hörenGlobal? (wie removeEventListener)
Nik
3
@ user1394625 ja, wie Sie in der Antwort den ngOnDestroyCode sehen können, beide listenund listenGlobaleine Funktion zurückgeben, dass beim Aufrufen / Ausführen der Listener entfernt wird. Wie Sie sehen, this.funcwird die von zurückgegebene Funktion gehalten, renderer.listenund wenn ich dies this.func()tue, entferne ich den Listener. Das gilt auch für listenGlobal.
Eric Martinez
@EricMartinez hat noch eine Frage an Sie ... wie kann ich auf das 'Ereignis' innerhalb der Funktion zugreifen, um Default () oder stopPropagation () zu verhindern
Nik
5

Ich finde das auch äußerst verwirrend. Wie @EricMartinez darauf hinweist, gibt Renderer2 listen () die Funktion zum Entfernen des Listeners zurück:

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

Wenn ich einen Listener hinzufüge

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

Ich würde erwarten, dass meine Funktion das ausführt, was ich beabsichtigt habe, nicht das Gegenteil, das den Listener entfernt.

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

In dem gegebenen Szenario wäre es tatsächlich sinnvoller, es so zu benennen:

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

Es muss einen guten Grund dafür geben, aber meiner Meinung nach ist es sehr irreführend und nicht intuitiv.

tahiche
quelle
3
Wenn Sie einen Listener hinzufügen, warum würden Sie dann erwarten, dass die durch Hinzufügen dieses Listeners zurückgegebene Funktion diesen Listener aufruft? Das macht für mich nicht viel Sinn. Der Sinn des Hinzufügens eines Listeners besteht darin, auf Ereignisse zu reagieren, die Sie nicht unbedingt programmgesteuert auslösen können. Ich denke, wenn Sie erwartet haben, dass diese Funktion Ihren Hörer aufruft, verstehen Sie die Hörer möglicherweise nicht vollständig.
Willwsharp
@ Tahiche Kumpel das ist wirklich verwirrend, danke für den Hinweis!
godblessstrawberry
Dies wird zurückgegeben, sodass Sie den Listener auch wieder entfernen können, wenn Sie Ihre Komponente später zerstören. Wenn Sie Listener hinzufügen, sollten Sie diese später entfernen, wenn Sie sie nicht mehr benötigen. Speichern Sie diesen Rückgabewert und rufen Sie ihn in Ihrer ngOnDestroyMethode auf. Ich gebe zu, dass es auf den ersten Blick verwirrend erscheinen mag, aber es ist tatsächlich eine sehr nützliche Funktion. Wie sonst nach dir selbst aufräumen?
Wilt
1

Ich werde ein StackBlitz-Beispiel und einen Kommentar zur Antwort von @tahiche hinzufügen.

Der Rückgabewert ist eine Funktion zum Entfernen des Ereignis-Listeners, nachdem Sie ihn hinzugefügt haben. Es wird empfohlen, Ereignis-Listener zu entfernen, wenn Sie sie nicht mehr benötigen. Sie können diesen Rückgabewert also speichern und in Ihrem aufrufenngOnDestroy Methode .

Ich gebe zu, dass es auf den ersten Blick verwirrend erscheinen mag, aber es ist tatsächlich eine sehr nützliche Funktion. Wie sonst können Sie nach sich selbst aufräumen?

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

Hier finden Sie einen StackBlitz , der zeigt, wie dies beim Klicken auf Ankerelemente funktionieren kann.

Ich habe einen Körper mit einem Bild wie folgt hinzugefügt: um
<img src="x" onerror="alert(1)"></div>
zu zeigen, dass das Desinfektionsmittel seine Arbeit macht.

Hier in dieser Geige finden Sie den gleichen Körper, der an einem befestigt ist, innerHTMLohne ihn zu desinfizieren, und er wird das Problem demonstrieren.

Verwelken
quelle
0

Hier ist meine Problemumgehung:

Ich habe eine Bibliothek mit Angular 6 erstellt. Ich habe eine allgemeine Komponente hinzugefügt, commonlib-headerdie in einer externen Anwendung so verwendet wird.

Beachten Sie , serviceReferencedie die Klasse ist (in der Komponente eingespritzt constructor(public serviceReference: MyService), die der verwendet commonlib-header), die den hält stringFunctionNameMethode:

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

Die Bibliothekskomponente ist so programmiert. Das dynamische Ereignis wird in der onClick(fn: any)Methode hinzugefügt :

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

Das wiederverwendbare 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
quelle