问题描述
我正在用ng2开发一个应用程序,我正在努力解决问题。我正在构建一个日历,您可以在其中选择日期范围,我需要对点击
& 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).
所以我想以正确的方式这样做,使用称为事件委托的方法。我的意思是,在父组件上附加一个click事件( month
),当它收到一个click事件时,只需检查它是否发生在 Day
组件 - 然后我会对此点击作出反应。当你传递选择器时,像jQuery这样的
参数。
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中一样。此外,它在这里失控 - 你在Day的代码中对Day的点击做出反应。
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?
提前致谢!
推荐答案
简介
我偶然发现在今天这个,我真的可以看到在许多应用程序中需要这个实现。现在我不能保证这是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 => month =>一天
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托管了一些天和日。组件什么都不做,只是将它显示为文本。
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>
这是非常标准的股票。
第1阶段
让我们看一下月份。 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!");
}
这是有效的,因为打字稿不关心事件是否是本机事件,它只是将一个新的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.
第2阶段
如果我们说如果会发生什么?想在 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);
}
不幸的是我们的回调函数在这里有点含糊不清,但是打字稿会抱怨如果另外命名,该属性不是 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.ts
中运行逻辑以按 event.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中的事件代表团的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!