Динамически добавлять прослушиватель событий

Я только начинаю возиться с Angular 2, и мне интересно, может ли кто-нибудь сказать мне, как лучше всего динамически добавлять и удалять прослушиватели событий из элементов.

У меня есть настроенный компонент. При щелчке по определенному элементу в шаблоне я хочу добавить слушателя для mousemove к другому элементу того же шаблона. Затем я хочу удалить этого слушателя при нажатии третьего элемента.

Я вроде как получил эту работу, просто используя простой Javascript для захвата элементов, а затем вызывая стандартный addEventListener(), но мне было интересно, есть ли более "Angular2.0" способ сделать это, что я должен изучить .


person popClingwrap    schedule 29.01.2016    source источник


Ответы (4)


Средство визуализации устарело в Angular 4.0.0-rc.1, прочтите обновление ниже

angular2 - использовать listen или listenGlobal из Renderer

Например, если вы хотите добавить событие щелчка к Компоненту, вы должны использовать Renderer и ElementRef (это также дает вам возможность использовать ViewChild или что-нибудь, что извлекает nativeElement)

constructor(elementRef: ElementRef, renderer: Renderer) {

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

Вы можете использовать listenGlobal, который даст вам доступ к document, body и т. Д.

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

Обратите внимание, что начиная с версии beta.2 и listen, и listenGlobal возвращают функцию для удаления слушателя (см. критические изменения из журнала изменений для бета-версии.2). Это сделано во избежание утечек памяти в больших приложениях (см. # 6686).

Итак, чтобы удалить динамически добавленный слушатель, мы должны назначить listen или listenGlobal переменной, которая будет содержать возвращаемую функцию, а затем мы ее выполним.

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

Вот plnkr с рабочим примером. Пример содержит использование listen и listenGlobal.

Использование RendererV2 с Angular 4.0.0-rc.1 + (Renderer2 начиная с 4.0.0-rc.3)

RendererV2 имеет больше нет listenGlobal функции для глобальных событий (документ, тело, окно). У него есть только listen функция, которая выполняет обе функции.

Для справки я копирую и вставляю исходный код реализации DOM Renderer, поскольку он может измениться (да, угловатый!).

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

Как видите, теперь он проверяет, передаем ли мы строку (документ, тело или окно), и в этом случае он будет использовать внутреннюю addGlobalEventListener функцию. В любом другом случае, когда мы передаем элемент (nativeElement), он будет использовать простой addEventListener

Чтобы удалить слушателя, это то же самое, что и Renderer в angular 2.x. listen возвращает функцию, затем вызывает эту функцию.

Пример

// 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 с Angular 4.0.0-rc.1 с использованием < strong> RendererV2

plnkr с Angular 4.0.0-rc.3 с использованием < strong> Renderer2

person Eric Martinez    schedule 29.01.2016
comment
Это всего лишь мой второй день работы с Angular2, и я только начал разбираться в v1, так что многое из этого довольно запутанно. Вы дали мне много материала для чтения, поэтому я закрываю этот вопрос и, несомненно, скоро вернусь с ОЧЕНЬ большим количеством связанных вопросов. Приветствую за подробный ответ :) - person popClingwrap; 29.01.2016
comment
@popClingwrap, вы также можете проверить HostListener. В документации проверьте директивы атрибутов в разделе Отвечайте на действия пользователя, чтобы увидеть, как используется host. - person Eric Martinez; 29.01.2016
comment
@EricMartinez есть ли способ перестать слушать, чтобы слушать или listenGlobal? (так же, как removeEventListener) - person Nik; 29.03.2016
comment
@ user1394625 да, как вы можете видеть в ответе кода ngOnDestroy, и listen, и listenGlobal возвращают функцию, которая при вызове / исполнении удаляется. Итак, как вы видите, this.func удерживает функцию, возвращаемую renderer.listen, и когда я делаю this.func(), я удаляю слушателя. То же самое и с listenGlobal. - person Eric Martinez; 29.03.2016
comment
@EricMartinez получил еще один вопрос к вам ... как я могу получить доступ к 'событию' внутри функции, чтобы предотвратитьDefault () или stopPropagation () - person Nik; 29.03.2016
comment
@EricMartinez, есть ли способ условно использовать [(ngModel)] или (blur)? Думаю, я как бы спрашиваю о динамическом способе установки эквивалента ngModelOptions в Angular 2. - person bodine; 29.04.2016
comment
@EricMartinez, как поживаешь listenGlobal('document.getElementById("myId")', ...)? Я не могу найти ни одного документа на listenGlobal - person maxbellec; 09.09.2016
comment
@EricMartinez, как я могу это сделать с конкретным компонентом документа? С помощью listenGlobal ('document.getElementById (myId)', ...)? - person emanuel07; 24.09.2016
comment
Я использовал этот ответ, чтобы распознать внешний щелчок для раскрывающегося списка заказной начальной загрузки. Мне это нужно, потому что у меня есть более сложные компоненты, чем элемент, и я не хочу закрывать раскрывающийся список, щелкнув один раз. Проблема в том, что я открываю раскрывающийся список с помощью другой кнопки вне его. В методе showDropDown () я регистрирую слушателя, а в методе hideDropDown () удаляю его. Проблема в том, что событие возникает, хотя я регистрирую его в методе showDropDown в первый раз. Я думаю, это потому, что событие щелчка документа возникает после метода, но как я могу получить событие после - person Franki1986; 22.02.2017
comment
Это решение в сочетании с событиями focusin и focusout - находка для обработки фокуса и размытия, когда вы не можете обработать нужный элемент. В моем случае я оборачиваю сторонний datepicker, и он не генерирует события размытия и фокуса. - person gearsandcode; 23.03.2017
comment
@EricMartinez, как прослушивать событие щелчка, когда элемент изначально недоступен и доступен только после того, как условие ngif имеет значение true? измененный пример из вашего с Angular 4.0.0-rc.3 с использованием Renderer2 - person keepscoding; 16.05.2017
comment
Можно ли прослушивать события DOM из службы или это считается плохой практикой? - Использование службы для прослушивания событий DOM в Angular - person Slava Fomin II; 09.06.2017
comment
Спасибо тебе за это! - person WhoIsCarlo; 08.03.2021

Я также считаю это крайне запутанным. как указывает @EricMartinez, Renderer2 listen () возвращает функцию для удаления слушателя:

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

Если я добавляю слушателя

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

Я ожидал, что моя функция выполнит то, что я задумал, а не полную противоположность - удаление слушателя.

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

В данном сценарии было бы разумнее назвать его так:

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

Для этого должна быть веская причина, но, на мой взгляд, это вводит в заблуждение и не является интуитивным.

person tahiche    schedule 26.02.2018
comment
Если вы добавляли слушателя, почему вы ожидали, что функция, возвращаемая добавлением этого слушателя, вызовет этот слушатель? Для меня это не имеет особого смысла. Весь смысл добавления слушателя заключается в том, чтобы реагировать на события, которые вы не можете запускать программно. Я думаю, если вы ожидали, что эта функция вызовет вашего слушателя, возможно, вы не полностью понимаете слушателей. - person Willwsharp; 29.03.2018
comment
@tahiche, приятель, это действительно сбивает с толку, спасибо, что указали на это! - person godblessstrawberry; 11.04.2018
comment
Он возвращает это, так что вы также можете снова удалить слушателя, когда позже вы уничтожите свой компонент. При добавлении слушателей считается хорошей практикой удалить их позже, когда они вам больше не нужны. Поэтому сохраните это возвращаемое значение и вызовите его в своем ngOnDestroy методе. Я признаю, что сначала это может показаться запутанным, но на самом деле это очень полезная функция. Как еще убрать за собой? - person Wilt; 18.03.2020

Я добавлю StackBlitz пример и комментарий к ответу @tahiche.

Возвращаемое значение - это функция для удаления прослушивателя событий после того, как вы его добавили. Считается хорошей практикой удалять прослушиватели событий, когда они вам больше не нужны. Таким образом, вы можете сохранить это возвращаемое значение и вызвать его в своем ngOnDestroy методе.

Я признаю, что сначала это может показаться запутанным, но на самом деле это очень полезная функция. Как еще можно убрать за собой?

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

Вы можете найти StackBlitz здесь, чтобы показать, как это может работать для улавливания щелчка по элементам привязки.

Я добавил тело с изображением, как показано ниже:
<img src="x" onerror="alert(1)"></div>
, чтобы показать, что дезинфицирующее средство выполняет свою работу.

Здесь, в этой скрипке, вы найдете то же тело, прикрепленное к innerHTML без его дезинфекции, и оно продемонстрирует проблема.

person Wilt    schedule 18.03.2020
comment
есть ли у removeEventListener интерфейс? как я понимаю можно было запустить еще removeEventListener.unsubscribe()? - person Mackelito; 22.04.2021
comment
@Mackelito unscubscribe обычно используется для Observables, у слушателей нет метода unsubscribe. Способ очистки слушателей - либо удалить их из EventManager (eventManager.remove(listener);), либо вызвать возвращенный метод, как указано выше. - person Wilt; 23.04.2021

Вот мой обходной путь:

Я создал библиотеку с помощью Angular 6. Я добавил общий компонент commonlib-header, который так используется во внешнем приложении.

Обратите внимание на serviceReference, который является классом (введенным в компонент constructor(public serviceReference: MyService), который использует commonlib-header), который содержит метод 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>

Библиотечный компонент запрограммирован так. Динамическое событие добавлено в метод 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)
  }

Многоразовый 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>
person Gus    schedule 26.06.2018