AngularJS event delegation

1,510 views
Skip to first unread message

nishant...@gmail.com

unread,
Sep 8, 2012, 10:08:09 AM9/8/12
to ang...@googlegroups.com
Hello all,

When using ng-click with ng-repeat, is a click handler added for each dom node? As far as I can tell digging through angular code, that seems to be the case. Has anyone give this a thought?

I put this together a directive, that does this. http://jsfiddle.net/nishp1/dqbaa/. I'd love to hear feedback on this. Basically, you add a dg-* to the parent container, and when a child is clicked, the handler will be invoked with the value of the property specified in the handler.

- Nish

Seth Messer

unread,
Sep 28, 2012, 4:15:02 PM9/28/12
to ang...@googlegroups.com, nishant...@gmail.com
Any thoughts on this from Igor, Misko, or the rest of the team?

Seth Messer

unread,
Oct 1, 2012, 3:04:50 PM10/1/12
to ang...@googlegroups.com, nishant...@gmail.com
Nish,

Having looked at your github example and on jsfiddle; have you given any thought to how you would setup delegates to target specific children of that table (for instance if you had multiple cells in a row, or even several buttons per row to handle various actions on that 'tweet')?

Very interested in this, but not seeing a clear cut way of applying the delegations to existing/new children of that container.

Thanks.

Nish Patel

unread,
Oct 1, 2012, 6:49:24 PM10/1/12
to Seth Messer, ang...@googlegroups.com
Yes, I was just thinking about this the other day. I was thinking making it so that you can specify one or more selectors and associated handler semicolon separated.

Something like: dg-click="selector1: handler1 ; selector2: handler2"

<tr dg-click=".one: oneClicked(); .two: twoClicked()">
  <td class="one"></td>
  <td class="two" ></td>
</tr>

Thoughts?

Nish

Justin L.

unread,
Oct 18, 2012, 2:53:19 AM10/18/12
to ang...@googlegroups.com, Seth Messer, nishant...@gmail.com
I ran into a performance concern and event delegation became a huge priority for me. For hours I was trying to figure out the best "Angular" way to do it, and I can across your directive on GitHub and want to thank you for it. I'm not sure how long it would have taken me to figure that one out. For anyone else that has this issue, here it is on GitHub: https://github.com/nishp1/angular-delegate-event

I really think the Angular guys should include some kind of event delegation within the framework itself. There are some common use cases that binding an event to each item on a page is a bad idea. Anyway, thanks!

Marco Alves

unread,
Oct 18, 2012, 10:37:40 AM10/18/12
to ang...@googlegroups.com, Seth Messer, nishant...@gmail.com
I'm going to try this directive in a situation where I have a list of ~1000 items which are clickable.

I'll report back how it went.

Nish

unread,
Oct 18, 2012, 12:50:10 PM10/18/12
to Marco Alves, ang...@googlegroups.com, Seth Messer
Great!

Been busy lately, but I will definitely have time this weekend to add support for target specific event handlers that I mentioned in the earlier post. Any feedback would be appreciated. 

Thank,
- Nish

Justin L.

unread,
Oct 18, 2012, 12:58:13 PM10/18/12
to ang...@googlegroups.com, Marco Alves, Seth Messer
Target specificity would be great! As I use this more, I'll try to provide more feedback. Thanks again!!

_     _     _     _

Justin Lowery

[512] 827-8487

__________________________________________

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To post to this group, send email to ang...@googlegroups.com.
To unsubscribe from this group, send email to angular+u...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular?hl=en.
 
 

Marco Alves

unread,
Oct 18, 2012, 1:57:07 PM10/18/12
to Nish, ang...@googlegroups.com, Seth Messer
I'm gettting a lot of errors and I can't see why.



TypeError: undefined is not a function at new ngDirective.controller (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:13540:5) at invoke (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:2795:28) at Object.instantiate (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:2805:23) at https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:4620:24 at https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:4201:17 at forEach (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:117:20) at nodeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:4186:11) at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:3838:14) at compositeLinkFn (https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:3841:12) at https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js:3750:30



Uncaught Error: 10 $digest() iterations reached. Aborting! Watchers fired in the last 5 iterations: [["fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"Manicure\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: true; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined"],["fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"Manicure\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: true; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined"],["fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"Manicure\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: true; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined"],["fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"Manicure\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: true; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined"],["fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"Manicure\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: true; oldVal: undefined","fn: function (context) {\n for(var i = 0, ii = length, part; i<ii; i++) {\n if (typeof (part = parts[i]) == 'function') {\n part = part(context);\n if (part == null || part == undefined) {\n part = '';\n } else if (typeof part != 'string') {\n part = toJson(part);\n }\n }\n concat[i] = part;\n }\n return concat.join('');\n }; newVal: \"\"; oldVal: undefined","showGroupHeader(entry, $index); newVal: false; oldVal: undefined"]]
--
Marco Manteigas Alves
tlm: 96 983 46 23
skype: marco.m.alves

Nish

unread,
Oct 18, 2012, 1:58:29 PM10/18/12
to Marco Alves, ang...@googlegroups.com, Seth Messer
Can you create a fiddle?

- Nish

Marco Alves

unread,
Oct 18, 2012, 2:04:09 PM10/18/12
to Nish, ang...@googlegroups.com, Seth Messer
Sorry. I'll try but it's not so easy because it's a bit convoluted.

I have a generic master-list directive that provides all the functionality of a list of items for the master part of a master-detail view.

The directive uses a ng-transclude so you can defined how render each item of the list -- for example, Cars are rendered one-way, RepairShops are rendered another way. I'll try to reproduce that in fiddle.

I just wondered if you could spot/hint the problem just by the error type.

rul...@gmail.com

unread,
Oct 18, 2012, 2:32:48 PM10/18/12
to ang...@googlegroups.com, Seth Messer, nishant...@gmail.com
I was thinking about this the other day as I was looking at derbyjs, which has an interesting way of handling this - they attach all event listeners to the document element and manually bubble the event from event.target until it hits a matching handler.

2 interesting things:
1. bubbling automatically stops as soon as you hit a handler, so they provide a "next" callback if you'd like to continue bubbling after handling an event
2. if an element doesn't have an id, they assign it a unique one with a custom prefix (this is because they map elements to handlers via the id attribute).

The advantage is that you get event delegation while still declaring event handlers exactly as you'd do now.

I'll try to put together a proof-of-concept sometime soon (but my time right now is constrained), but here's a quick sketch of what this could look like:

- define an angular module to contain a "$delegate" service and the various specific event-handling directives that will be necessary
- a directive like "click" would obviously depend on the $delegate service, and when called on an element, would:
    1. add an id if necessary
    2. call something like $delegate("click", id, fn), where fn would be the scope fn specified by the user
- and then it's the $delegate's job to attach the handler to the document element and dispatch the event accordingly

On Monday, October 1, 2012 5:49:28 PM UTC-5, Nish Patel wrote:
Reply all
Reply to author
Forward
0 new messages