Angular2中的事件代表团

Mic*_*zyk 15 javascript typescript angular2-template angular

我正在开发一个ng2应用程序,我正在努力解决问题.我建立一个日历,您可以选择一个日期范围,我需要作出反应上clickmouseenter/mouseleave上一天的细胞活动.所以我有一个像这样的代码(简化):

calendar.component.html

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

但是这给了我数百个独立的事件监听器在浏览器的内存中(每天的单元格获得3个事件监听器,我一次最多可以显示12个月,因此它将超过1k的监听器).

因此,我希望使用名为"事件委托"的方法"正确的方式".我的意思是,在父组件(month)上附加一个click事件,当它收到一个click事件时,只需检查它是否在Day组件上发生- 然后我就会对这个点击作出反应.当你传递参数时,像jQuery这样的东西在on()方法中selector.

但我是通过在处理程序代码中原生地引用DOM元素来实现的:

month.component.ts

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

我的同事拒绝了我的想法,因为 - 正如他们所说的那样 - "在NG2中必须有一种更简单,更恰当的方法来处理这个问题;就像在jQuery中一样.此外,它在这里失控 - 你对Day的点击做出反应在月的代码中."

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

提前致谢!

Zze*_*Zze 2

介绍

我今天偶然发现了这一点,我确实可以在很多应用程序中看到这种实现的必要性。现在我不能保证这是 100% 最好的技术,但是我已经竭尽全力使这种方法尽可能地受到角度启发。

我提出的方法有两个阶段。第 1 阶段和第 2 阶段总计都会增加years * months + years * months * days,因此在 1 年内您将有12 + 365事件。


舞台范围

第 1 阶段:将单击月份时的事件委托到单击的实际日期,而不要求当天发生事件。
第 2 阶段:将所选日期传播回月份。

在深入研究之前,该应用程序由 3 个组件组成,这些组件按以下顺序嵌套:app => month => day


这就是所需的全部 html。app.component 托管多个月份,month.component 托管多个天,而 day.component 不执行任何操作,只是将日期显示为文本。

应用程序组件.html

<app-month *ngFor="let month of months" [data-month]="month"></app-month>
Run Code Online (Sandbox Code Playgroud)

月.component.html

<app-day *ngFor="let day of days" [data-day]="day">{{day}}</app-day>
Run Code Online (Sandbox Code Playgroud)

day.component.html

<ng-content></ng-content>
Run Code Online (Sandbox Code Playgroud)

这是相当标准的东西。


阶段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'));
  }
}
Run Code Online (Sandbox Code Playgroud)

在第一阶段和第二阶段中,只有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!");
 }
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为 typescript 不关心事件是否是本机的,它只会将一个新的 javascript 事件绑定到元素,从而允许我们event.target.dispatchEvent像上面在month.component.ts.

第一阶段到此结束,我们现在成功地将每月的活动委托给每天的活动。


第二阶段

那么,如果我们说想要在委托事件中运行一点逻辑,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);
  }
Run Code Online (Sandbox Code Playgroud)

剩下的就是在day.component.ts...... 中调用这个函数。

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

不幸的是,我们的回调函数在这里的命名有点含糊,但是如果以其他方式命名,打字稿会抱怨该属性不是定义的对象文字CustomEventInit


多事件漏斗

这种方法的另一个很酷的事情是,您永远不必定义超过此数量的事件,因为您可以通过此委托汇集所有事件,然后在其中运行逻辑day.component.ts来过滤event.type...

月份.组件.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 }));
    }
  }
Run Code Online (Sandbox Code Playgroud)

日组件.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)
    }
  }
Run Code Online (Sandbox Code Playgroud)