Angular2 中的事件委托 [英] Event delegation in Angular2

查看:23
本文介绍了Angular2 中的事件委托的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm developing an app in ng2 and I'm struggling with something. I'm building a calendar where you can pick a date-range and I need to react on click & mouseenter/mouseleave events on day cells. So I have a code (simplified) like this:

calendar.component.html

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

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

But this gives me hundreds of separate event listeners in the browser's memory (each day's cell gets 3 event listeners, and I can have up to 12 months displayed at a time so it would be over 1k of listeners).

所以我想以正确的方式"来做,使用称为事件委托"的方法.我的意思是,在父组件(month)上附加一个点击事件,当它收到一个点击事件时,只需检查它是否发生在 Day 组件上 - 只有这样我才会对这次点击做出反应.当你将 selector 参数.

So I wanted to do it "the proper way", using the method called "event delegation". I mean, attach a click event on the parent component (month) and when it receives a click event, simply check whether it occurred on Day component - and only then I would react to this click. Something like jQuery does in it's on() method when you pass it the selector parameter.

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

But I was doing it by referencing the DOM elements natively in the handler's code:

month.component.ts

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

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

and my colleagues rejected my idea, since - as they said - "There must be a simpler, proper way of handling this in NG2; like there is in jQuery. Besides, it's getting out of control here - you're reacting to Day's clicks in Month's code."

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

So, my question is, is there a better way? Or am I trying to solve a problem which I shouldn't bother solving anymore, since users' devices get more and more memory/processing power everyday?

提前致谢!

推荐答案

Intro

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

I stumbled across this today, and I can really see the need for this implementation in a lot of applications. Now I can't vouch that this is 100% the best technique however I have gone out of my way to make this approach as angular inspired as possible.

我提出的方法有两个阶段.第 1 阶段和第 2 阶段将总共添加 年 * 月 + 年 * 月 * 天,因此 1 年您将有 12 + 365 个事件.<小时>舞台范围

The approach that I have come up with has 2 stages. Both stage 1 and stage 2 will add a total of years * months + years * months * days, so for 1 year you will have 12 + 365 events.


Stage Scope

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

Stage 1: Delegate events from when a month is clicked down into the actual day which was clicked without requiring an event on the day.
Stage 2: Propagate the chosen day back to the month.

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

Just before delving in, the application consists of 3 components which are nested in the following order: app => month => day

这是所有需要的 html.app.component 承载数个月,month.component 承载数天,而 day.component 仅将日期显示为文本.

This is all the html that is required. app.component hosts a number of months, month.component hosts a number of days and day.component does nothing but display it's day as text.

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.

Let's have a look at the month.component.ts where we want to delegate our event from.

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

In both stage 1 and 2, there is only 1 caveat and that is; if you have nested child elements within your day.component.html, you will need to either implement bubbling for this, better logic in that if statement, or a quick hack would be.. in day.component.css :host *{pointer-events: none;}

<小时>现在我们需要告诉我们的 day.component 期待我们的 delegateEvent 事件.所以在 day.component.ts 中你所要做的(以最有角度的方式)是......


Now we need to tell our day.component to be expecting our delegateEvent event. So in day.component.ts all you have to do (in the most angular way possible) is...

@HostListener('delegateEvent', ['$event'])
 public onEvent() {
   console.log("i've been clicked via a delegate!");
 }

这是有效的,因为 typescript 不关心事件是否是原生的,它只会将一个新的 javascript 事件绑定到元素,从而允许我们通过 event.target.dispatchEvent 来原生地"调用它 就像我们上面在 month.component.ts 中所做的那样.

This works because typescript doesn't care about whether the event is native or not, it will just bind a new javascript event to the element and thus allows us to call it "natively" via event.target.dispatchEvent as we do above in month.component.ts.

第 1 阶段结束,我们现在成功地将事件从我们的月份委派到我们的日子.

That concludes Stage 1, we are now successfully delegating events from our month to our days.

<小时>第二阶段

如果我们说要在 day.component 内的委托事件中运行一点逻辑,然后将其返回给 month.component 会发生什么 - 以便然后它可以在非常面向对象的方法中继续使用自己的功能吗?幸运的是,我们可以很容易地实现这一点!

So what happens if we say want to run a little bit of logic in our delegated event within day.component and then return it to month.component - so that it can then carry on with its own functionality in a very object oriented method? Well fortunately, we can very easily implement this!

month.component.ts 中更新为以下内容.唯一改变的是我们现在将通过事件调用传递一个函数,并定义我们的回调函数.

In month.component.ts update to the following. All that has changed is that we are now going to pass a function with our event invocation and we defined our callback function.

@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 中调用这个函数...

All that is left is to invoke this function within 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 的定义对象字面量.

Unfortunately our callback function is a little ambiguously named here, however typescript will complain about the property not being a defined object literal for CustomEventInit if named otherwise.

<小时>多事件漏斗

关于这种方法的另一个很酷的事情是你永远不必定义超过这个数量的事件,因为你可以通过这个委托汇集所有事件,然后在 day.component.tsevent.type...

The other cool thing about this approach is that you should never have to define more than this number of events because you can just funnel all events through this delegation and then run logic within day.component.ts to filter by 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)
    }
  }

这篇关于Angular2 中的事件委托的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆