【问题标题】:Event delegation in Angular2Angular2 中的事件委托
【发布时间】:2016-11-08 09:57:10
【问题描述】:

我正在用 ng2 开发一个应用程序,但我遇到了一些问题。我正在构建一个日历,您可以在其中选择一个日期范围,我需要对白天单元格上的 clickmouseenter/mouseleave 事件做出反应。所以我有一个这样的代码(简化):

calendar.component.html

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

但这在浏览器的内存中为我提供了数百个单独的事件侦听器(每天的单元格有 3 个事件侦听器,我一次最多可以显示 12 个月,因此它会超过 1k 个侦听器)。

所以我想以“正确的方式”来做,使用称为“事件委托”的方法。我的意思是,在父组件(month)上附加一个点击事件,当它接收到点击事件时,只需检查它是否发生在Day 组件上——然后我才会对这个点击做出反应。当您将 selector 参数传递给它时,像 jQuery 这样的东西会在 on() method 中执行。

但我是通过在处理程序代码中本地引用 DOM 元素来做到这一点的:

month.component.ts

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

我的同事拒绝了我的想法,因为——正如他们所说——“在 NG2 中必须有一种更简单、正确的方法来处理这个问题;就像在 jQuery 中一样。此外,它在这里失控了——你正在做出反应在 Month 的代码中添加 Day 的点击次数。”

所以,我的问题是,有没有更好的方法?还是我试图解决一个我不应该再费心解决的问题,因为用户的设备每天都获得越来越多的内存/处理能力?

提前致谢!

【问题讨论】:

  • 有独立的模块可以处理这个问题。我发现“dom-delegate”工作得很好。据我所知,ng2 没有任何内置功能。
  • 如果这是一个问题......这将是一个角度的优化问题。您不必担心聚合事件以进行优化。
  • 几个月前我遇到了类似的问题,据我所知,您描述的方法是使用原始角度执行此操作的最佳方法。检查具有较少事件侦听器的点击目标。我唯一建议的是将逻辑放在服务或指令中(指令可能会更好,因为您希望它在每个月的 div 上)这样代码是干净的并且与组件分离。毕竟是html逻辑,不是组件逻辑

标签: javascript typescript angular angular2-template


【解决方案1】:

简介

我今天偶然发现了这个,我真的可以看到很多应用程序都需要这个实现。现在我不能保证这是 100% 最好的技术,但是我已经竭尽全力使这种方法尽可能有角度的启发。

我提出的方法分为两个阶段。第 1 阶段和第 2 阶段将总共添加 years * months + years * months * days,因此在 1 年内您将拥有 12 + 365 事件。


舞台范围

第 1 阶段: 将事件从点击月份开始到点击的实际日期,而不需要当天的事件。
第 2 阶段:将所选日期传播回月份。

在深入研究之前,该应用程序由 3 个组件组成,这些组件按以下顺序嵌套:app =&gt; month =&gt; 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>

这是相当标准的东西。


第一阶段

让我们看一下我们要从中委派事件的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 阶段,只有 1 个警告,那就是;如果您在 day.component.html 中嵌套了子元素,您将需要为此实现冒泡,在该 if 语句中实现更好的逻辑,或者快速破解将是......在day.component.css :host *{pointer-events: none;}


现在我们需要告诉 day.component 期待我们的 delegateEvent 事件。所以在day.component.ts 中,你所要做的(以最有角度的方式)就是......
@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)
    }
  }

【讨论】:

  • 要么我不明白你的概念,要么你误解了我想要达到的目标。我只想有 N 个事件侦听器,其中 N 是月数。您的方法创建的事件侦听器数量与 DayComponents 上的 HostBinding 单击事件的数量完全相同。只是你在绑定自定义事件。
  • 多事件,然而,才是真正能带来利润的时候:)
  • @MichalLeszczyk 它并没有完全达到 N 标记,但是是一个显着的改进,并且当您添加新的事件类型时也不会成倍增加。它并不完美,但我认为它在正确的轨道上!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-18
  • 1970-01-01
  • 1970-01-01
  • 2012-02-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多