admin管理员组文章数量:1425665
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:
calendarponent.html
<month>
<day *ngFor="let day of days" (click)="handleClick()"
(mouseenter)="handleMouseEnter()"
(mouseleave)="handleMouseLeave()"
[innerHTML]="day"></day>
</month>
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).
So I wanted to do it "the proper way", using the method called "event delegation". I mean, attach a click event on the parent ponent (month
) and when it receives a click event, simply check whether it occurred on Day
ponent - 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.
But I was doing it by referencing the DOM elements natively in the handler's code:
monthponent.ts
private handleClick(event) {
if (event.target.tagName === 'DAY') {
// handle day click
} else {
// handle other cases
}
}
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?
Thanks in advance!
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.ponent.html
<month>
<day *ngFor="let day of days" (click)="handleClick()"
(mouseenter)="handleMouseEnter()"
(mouseleave)="handleMouseLeave()"
[innerHTML]="day"></day>
</month>
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).
So I wanted to do it "the proper way", using the method called "event delegation". I mean, attach a click event on the parent ponent (month
) and when it receives a click event, simply check whether it occurred on Day
ponent - 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.
But I was doing it by referencing the DOM elements natively in the handler's code:
month.ponent.ts
private handleClick(event) {
if (event.target.tagName === 'DAY') {
// handle day click
} else {
// handle other cases
}
}
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?
Thanks in advance!
Share Improve this question edited Jul 7, 2017 at 6:04 yurzui 215k32 gold badges447 silver badges411 bronze badges asked Jul 6, 2016 at 11:04 Michal LeszczykMichal Leszczyk 1,89915 silver badges19 bronze badges 3- 1 There are standalone modules which handles this. I find "dom-delegate" to work very well. There's nothing built-in in ng2 as far as I know. – Chrillewoodz Commented Jul 6, 2016 at 12:26
- 1 If this is an issue... it would be an optimization problem in angular. You shouldn't have to worry about aggregating events to optimize. – Jacob Roberts Commented Jul 12, 2017 at 19:39
- 1 I had a similar issue a few months back, from what I know, the method you described is the best way to do this with raw angular. checking the click targets with less event listeners. the only thing I would suggest is putting the logic in a service or directive (directive might work better since you want it on each month div) that way the code is clean and separated from the ponent. After all, it's html logic, not ponent logic – FussinHussin Commented Sep 27, 2017 at 16:21
1 Answer
Reset to default 2Intro
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.
The approach that I have e 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
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.
Just before delving in, the application consists of 3 ponents which are nested in the following order: app => month => day
This is all the html that is required. app.ponent hosts a number of months, month.ponent hosts a number of days and day.ponent does nothing but display it's day as text.
app.ponent.html
<app-month *ngFor="let month of months" [data-month]="month"></app-month>
month.ponent.html
<app-day *ngFor="let day of days" [data-day]="day">{{day}}</app-day>
day.ponent.html
<ng-content></ng-content>
This is pretty stock standard stuff.
Stage 1
Let's have a look at the month.ponent.ts
where we want to delegate our event from.
// obtain a reference to the month(this) element
constructor(private element: ElementRef) { }
// when this ponent 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'));
}
}
In both stage 1 and 2, there is only 1 caveat and that is; if you have nested child elements within your day.ponent.html
, you will need to either implement bubbling for this, better logic in that if statement, or a quick hack would be.. in day.ponent.css
:host *{pointer-events: none;}
Now we need to tell our day.ponent to be expecting our
delegateEvent
event. So in day.ponent.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!");
}
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.ponent.ts
.
That concludes Stage 1, we are now successfully delegating events from our month to our days.
Stage 2
So what happens if we say want to run a little bit of logic in our delegated event within day.ponent
and then return it to month.ponent
- 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!
In month.ponent.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);
}
All that is left is to invoke this function within day.ponent.ts
...
public onEvent(event) {
// run whatever logic you like,
//return whatever data you like to month.ponent
event.detail(this.day);
}
Unfortunately our callback function is a little ambiguously named here, however typescript will plain about the property not being a defined object literal for CustomEventInit
if named otherwise.
Multiple Event Funnel
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.ponent.ts
to filter by event.type
...
month.ponent.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.ponent.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)
}
}
本文标签: javascriptEvent delegation in Angular2Stack Overflow
版权声明:本文标题:javascript - Event delegation in Angular2 - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1745456783a2659139.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论