Динамічно додайте слухача подій


143

Я тільки починаю возитися з Angular 2 і мені цікаво, чи хтось може підказати мені найкращий спосіб динамічно додавати та видаляти слухачів подій з елементів.

У мене налаштований компонент. Коли клацнути певний елемент у шаблоні, я хочу додати слухача mousemoveдо іншого елемента того ж шаблону. Потім я хочу видалити цей слухач, коли натиснутий третій елемент.

Я начебто це працював просто за допомогою простого Javascript, щоб захопити елементи, а потім зателефонувавши до стандарту, addEventListener()але я поцікавився, чи існує більш " Angular2.0 " спосіб цього зробити, що я повинен шукати.

Відповіді:


262

Renderer застарілий у Angular 4.0.0-rc.1, читайте оновлення нижче

Спосіб angular2 полягає у використанні listenабо listenGlobalвід рендерера

Наприклад, якщо ви хочете додати подію клацання до компонента, вам потрібно використовувати 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повернути функцію для видалення слухача (див. Розділ " Розрив змін " із журналу змін для beta.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 з кутовим 4.0.0-rc.1 + (Renderer2 з 4.0.0-rc.3)

  • 25.02.2017 : Rendererзастаріло, тепер нам слід скористатися RendererV2(див. Рядок нижче). Див фіксації .

  • 03.10.2017 : RendererV2перейменовано на Renderer2. Дивіться переломні зміни .

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 у кутовій 2.х. 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 з кутовим 4.0.0-rc.1 з використанням RendererV2

plnkr з кутовим 4.0.0-rc.3 за допомогою Renderer2


Це лише мій другий день з Angular2, і я ледве почав отримувати голову навколо v1, тому багато цього досить заплутано. Ти дав мені гарний набір матеріалів, щоб прочитати, хоча тому я закриваю цю, і, безсумнівно, незабаром повернуся з ЛОТОМИ більше пов'язаних питань.
Привіт

3
@popClingwrap Ви також можете перевірити HostListener . У документах перевірте директиви щодо атрибутів у розділі Відповісти на дії користувача, щоб побачити, як hostвикористовується також.
Ерік Мартінес

@EricMartinez є спосіб зупинити прослуховування або слухати, або слухатиGlobal? (те саме, що видалити deleteEventListener)
Нік

3
@ user1394625 так, як ви бачите у відповіді ngOnDestroyкод, і те, listenі listenGlobalповернути функцію, яка при виклику / виконанні слухача видаляється. Отже, як ви бачите this.func, утримує функцію, повернуту renderer.listenі коли я це роблю, this.func()я видаляю слухача. Те ж саме стосується listenGlobal.
Ерік Мартінес

@EricMartinez отримав ще одне питання до вас ... як я можу отримати доступ до "події" всередині функції, щоб запобігти Default () або stopPropagation ()
Nik

5

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

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

Це має бути вагомою причиною, але, на мою думку, це дуже оманливо і не інтуїтивно.


3
Якщо ви додавали слухача, чому б ви очікували, що функція повернеться, додавши, що слухач викликає цього слухача? Це не має для мене особливого сенсу. Весь сенс додавання слухача полягає в тому, щоб реагувати на події, які не можна обов'язково викликати програмно. Я думаю, якби ви очікували, що ця функція викликатиме свого слухача, ви, можливо, не будете розуміти слухачів повністю.
Віллсхарп

@tahiche товариш це дуже заплутано, дякую, що вказав на це!
godblessstrawberry

Він повертає це, тож ви також зможете знову видалити слухача, коли згодом ви знищите свій компонент. При додаванні слухачів вважається хорошою практикою видаляти їх пізніше, коли вони вам більше не потрібні. Тому зберігайте це повернене значення і називайте його всередині вашого ngOnDestroyметоду. Я визнаю, що спочатку це може здатися заплутаним, але насправді це дуже корисна функція. Як ще прибирати після себе?
Заповіт

1

Я додам приклад 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без санітарії, і він продемонструє проблему.


0

Ось мій спосіб вирішення:

Я створив бібліотеку з кутовим 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.