Подтвердить что ты не робот

Делегирование событий в Angular2

Я разрабатываю приложение в ng2, и я борюсь с чем-то. Я создаю календарь, в котором вы можете выбрать диапазон дат, и мне нужно реагировать на события click и mouseenter/mouseleave на ячейках дня. Поэтому у меня есть код (упрощенный):

calendar.component.html

<month>
    <day *ngFor="let day of days" (click)="handleClick()" 
         (mouseenter)="handleMouseEnter()" 
         (mouseleave)="handleMouseLeave()" 
         [innerHTML]="day"></day>
</month>

Но это дает мне сотни отдельных прослушивателей событий в памяти браузера (каждая ячейка дня получает 3 прослушивателя событий, и я могу отображать до 12 месяцев одновременно, так что это будет более 1k слушателей).

Итак, я хотел сделать это "надлежащим образом", используя метод "делегирование событий". Я имею в виду, присоединить событие click к родительскому компоненту (month), и когда он получит событие click, просто проверьте, произошло ли это на компоненте Day, и только тогда я бы отреагировал на этот клик. Что-то вроде jQuery делает в нем on() метод, когда вы передаете ему параметр selector.

Но я делал это, ссылаясь на элементы DOM изначально в коде обработчика:

month.component.ts

private handleClick(event) {
    if (event.target.tagName === 'DAY') {
        // handle day click
    } else {
        // handle other cases
    }
}

и мои коллеги отвергли мою идею, поскольку, как они сказали, "должен быть более простой и правильный способ обработки этого в NG2, как в jQuery. Кроме того, он выходит из-под контроля здесь - вы реагируете to Day clicks в коде месяца."

Итак, мой вопрос: есть ли лучший способ? Или я пытаюсь решить проблему, которую я больше не должен беспокоить, поскольку устройства пользователей все больше и больше потребляют память/обработку?

Спасибо заранее!

4b9b3361

Ответ 1

Введение

Я наткнулся на это сегодня, и я действительно вижу необходимость в этой реализации во многих приложениях. Теперь я не могу ручаться, что это на 100% лучшая техника, однако я ушел с пути, чтобы сделать этот подход максимально возможным angular.

Подход, который я придумал, состоит из двух этапов. Оба этапа 1 и 2 будут добавлены в общей сложности years * months + years * months * days, поэтому в течение 1 года у вас будут события 12 + 365.


Сфера действия

Этап 1: Делегировать события с того момента, когда в течение дня кликается на день, который был нажат без необходимости события в день.
Этап 2: Продвигайте выбранный день до месяца.

Перед тем, как вникать, приложение состоит из трех компонентов, которые вложены в следующем порядке: app => month => day


Это весь html, который требуется. В app.component есть несколько месяцев, а на сайте month.component есть несколько дней, а day.component ничего не делает, кроме как отображает его как текст.

app.component.html

<app-month *ngFor="let month of months" [data-month]="month"></app-month>

month.component.html

<app-day *ngFor="let day of days" [data-day]="day">{{day}}</app-day>

day.component.html

<ng-content></ng-content>

Это довольно стандартный материал.


Этап 1

Посмотрим на month.component.ts, где мы хотим делегировать наше событие.

// obtain a reference to the month(this) element 
constructor(private element: ElementRef) { }

// when this component is clicked...
@HostListener('click', ['$event'])
public onMonthClick(event) {
  // check to see whether the target element was a child or if it was in-fact this element
  if (event.target != this.element.nativeElement) {
    // if it was a child, then delegate our event to it.
    // this is a little bit of javascript trickery where we are going to dispatch a custom event named 'delegateclick' on the target.
    event.target.dispatchEvent(new CustomEvent('delegateEvent'));
  }
}

На обоих этапах 1 и 2 существует only 1, и это; если у вас есть вложенные дочерние элементы в вашем day.component.html, вам понадобится либо реализовать барботирование для этого, лучшая логика в этом операторе if, либо быстрый взлом будет... в day.component.css :host *{pointer-events: none;}


Теперь нам нужно сказать нашему day.component, что мы ожидаем нашего события delegateEvent. Таким образом, в day.component.ts все, что вам нужно сделать (максимально возможное angular) - это...
@HostListener('delegateEvent', ['$event'])
 public onEvent() {
   console.log("i've been clicked via a delegate!");
 }

Это работает, потому что typescript не заботится о том, является ли это событие родным или нет, оно просто привязывает новое событие javascript к элементу и, таким образом, позволяет называть его "изначально" через event.target.dispatchEvent, как мы это делаем выше в month.component.ts.

Это завершает этап 1, мы теперь успешно делегируем события из нашего месяца в наши дни.


Этап 2

Итак, что произойдет, если мы скажем, хотим запустить немного логики в нашем делегированном событии в day.component, а затем вернуть его в month.component - чтобы он мог продолжить свою собственную функциональность в очень объектно-ориентированном метод? К счастью, мы можем очень легко реализовать это!

В month.component.ts обновите следующее. Все, что изменилось, состоит в том, что мы теперь будем передавать функцию с помощью нашего вызова события, и мы определили нашу функцию обратного вызова.

@HostListener('click', ['$event'])
  public onMonthClick(event) {  
    if (event.target != this.element.nativeElement) {
      event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback}));
    }
  }

  public eventDelegateCallback(data) {
    console.log(data);
  }

Все, что осталось, это вызвать эту функцию в day.component.ts...

public onEvent(event) {
    // run whatever logic you like, 
    //return whatever data you like to month.component
    event.detail(this.day);
}

К сожалению, наша функция обратного вызова немного неоднозначно названа здесь, однако typescript будет жаловаться на то, что свойство не является литералом определенного объекта для CustomEventInit, если именовано иначе.


Последовательность нескольких событий

Другая интересная вещь об этом подходе заключается в том, что вам никогда не придется определять больше, чем это количество событий, потому что вы можете просто соединить все события с помощью этой делегирования, а затем запустить логику в day.component.ts для фильтрации с помощью event.type..

month.component.ts

@HostListener('click', ['$event'])
@HostListener('mouseover', ['$event'])
@HostListener('mouseout', ['$event'])
  public onMonthEvent(event) {  
    if (event.target != this.element.nativeElement) {
      event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback }));
    }
  }

day.component.ts

private eventDelegateCallback: any;

@HostListener('delegateEvent', ['$event'])
  public onEvent(event) {
    this.eventDelegateCallback = event.detail;
    if(event.type == "click"){
       // run click stuff
       this.eventDelegateCallback(this.day)
    }
  }