Dynamiczne dodawanie detektora zdarzeń w Angular 2

Właśnie zaczynam zadzierać z Angular 2 i zastanawiam się, czy ktoś może mi powiedzieć, jak najlepiej dynamicznie dodawać i usuwać słuchacze zdarzeń z elementów.

Mam ustawiony komponent. Po kliknięciu określonego elementu w szablonie chcę dodać listener dla mousemove do innego elementu tego samego szablonu. Następnie chcę usunąć tego słuchacza, gdy trzeci element jest kliknięty.

Tak jakby to działało, używając zwykłego Javascript, aby pobrać elementy, a następnie wywołanie standardu addEventListener() ale zastanawiałem się, czy nie ma bardziej "Angular2.0" sposób, w jaki to robię, że powinienem się przyjrzeć.

Pozdrawiam za wszelkie rady:) [3]}
 116
Author: isherwood, 2016-01-29

3 answers

Renderer został wycofany w Angular 4.0.0-RC.1, przeczytaj aktualizację poniżej

angular2 sposób jest użycie listen lub listenGlobal z Renderer

Na przykład, jeśli chcesz dodać zdarzenie click do komponentu, musisz użyć renderera i ElementRef (daje to również opcję użycia ViewChild lub czegokolwiek, co pobiera nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

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

Możesz użyć listenGlobal, który da ci dostęp do document, body, itd.

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

Zauważ, że od wersji beta.2 zarówno listen, jak i listenGlobalzwracają funkcję, która usuwa słuchacz (zobacz sekcję breaking changes W changelog dla wersji beta.2). Ma to na celu uniknięcie wycieków pamięci w dużych aplikacjach (patrz #6686).

Więc aby usunąć detektor dodaliśmy dynamicznie musimy przypisać listen lub listenGlobal do zmiennej, która będzie zawierała zwracaną funkcję, a następnie ją wykonujemy.

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

Oto plnkr Z działającym przykładem. Na przykład zawiera użycie listen i listenGlobal.

Używanie RendererV2 z Angular 4.0.0-RC.1+ (Renderer2 od 4.0.0-rc.3)

  • 25/02/2017: Renderer został przestarzały, teraz powinniśmy użyć RendererV2 (patrz linia poniżej). Zobacz commit .

  • 10/03/2017: RendererV2 został przemianowany na Renderer2. Zobacz przełomowe zmiany .

RendererV2 nie ma więcej funkcji listenGlobal dla zdarzeń globalnych (document, body, window). Posiada tylko listen funkcję, która spełnia obie funkcje.

W celach informacyjnych kopiuję i wklejam Kod źródłowy implementacji renderera DOM, ponieważ może się on zmienić (tak, jest kanciasty!).

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

Jak widzisz, teraz sprawdza, czy przekazujemy ciąg znaków (dokument, treść lub okno), w którym to przypadku użyje wewnętrznej funkcji addGlobalEventListener. W każdym innym przypadku, gdy mijamy element (nativeElement) użyje prostej addEventListener

Aby usunąć słuchacza jest tak samo jak z Renderer w angular 2.x. listen zwraca funkcję, a następnie wywołuje tę funkcję.

Przykład

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

Plnkr Z Angular 4.0.0-rc.3 using Renderer2

 215
Author: Eric Martinez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-10 21:22:48

Uważam to za bardzo mylące. jak @EricMartinez wskazuje renderer2 listen () zwraca funkcję usuwania słuchacza:

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

If im adding a listener

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

Oczekuję, że moja funkcja wykona to, co zamierzałem, a nie całkowite przeciwieństwo, jakim jest usunięcie słuchacza.

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

W danym scenariuszu, itp rzeczywiście bardziej sensowne jest nazwanie go tak:

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

Musi być ku temu dobry powód, ale moim zdaniem jest to bardzo mylące i nie intuicyjne.

 2
Author: tahiche,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-26 09:34:59

Oto moje obejście:

Stworzyłem bibliotekę z Angular 6. Dodałem wspólny komponent commonlib-header, który jest używany w ten sposób w zewnętrznej aplikacji.

Zwróć uwagę na serviceReference, która jest klasą (wtryskiwaną w komponent constructor(public serviceReference: MyService), który używa metody commonlib-header), która przechowuje metodę 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>

Komponent biblioteki jest zaprogramowany w ten sposób. Zdarzenie dynamiczne jest dodawane w metodzie 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)
  }

Wielokrotnego użytku 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>
 0
Author: Gus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-26 18:45:27