Angular.js: Change ngClick to use event delegation instead of standard events

Created on 14 Nov 2012  Â·  18Comments  Â·  Source: angular/angular.js

I'm sure that this has been talked about before, but I'm wondering why event delegation hasn't been used for some of the main angular DOM events such as onClick, onDblClick, onMouseDown, onMouseUp, etc...

If we delegate each of these events then there would be a nice performance upgrade for angular since each of the ngEVENT directives won't have to register their own copy of the DOM event.

This can be done by having one event for each type and then figuring out the in betweens with some JavaScript CSS selectors and scope checking.

I forked over the code, installed the test dependencies and looked around in the repo to find where the onClick directive is set. So far so good, however, turns out that jQLite (the angular one) doesn't include the .delegate (or .live) jquery function in it.

I'm sure I could get this to work since and if anything doesn't work then it looks like there is plenty of test specs to catch the bug(s).

So could we start by adding element.delegate to JQLite?

misc core investigation perf

Most helpful comment

Hi,
I investigated event delegation for AngularJS with the following results and benchmarks:

Possible directives that could use event delegation

  • ngClick, ...: directives that evaluate an expression when an event happens
  • $location: Already implements event delegation using the click event (see here).
  • a[href]: Has a link function to prevent clicks on empty links (see here). The link function could be removed with event delegation in $location.
  • ngModel: the link function of the directive cannot be removed, only the installation of the event listener.

Possible benefits

  • performance: No need to set up an event listener per element, and also no need to call the link function of the event directive
  • memory: Less memory consumption because of less closures

Possible problems

  • Installing a touchstart event listener on document can cause jank during scrolling (see here).
    Reason: The browser needs to call the touchstart handler to see whether it event.preventDefault()ed before it can start scrolling. Same problem when using gesture events from a library like ngTouch or hammer.js: Installing a gesture listener for e.g. tap on the document requires the library to install a touchstart event listener on the document as well
  • mobile Safari: for click event to bubble up there needs to be at least one click handler directly on the element (although the handler can be empty). E.g. el.addEventLisener('click', function() {}, false). See here.
    Note: the real click event is probably not very important for mobile apps as they want to use the touchxxx events and a gesture library like ngTouch or hammer.js to get faster feedback to user input (e.g. no 300ms delay)
  • Some events don't bubble, e.g. blur and focus
    Workaround: could have a black list or use capturing event listeners
  • Does not work well with ShadowDOM as not all events bubble through the ShadowDOM boundary, see here. E.g. change event is stopped.
    Note: This is not an issue for AngularJS 1.x, but will be important for AngularJS 2.x
  • Mobile browsers use installed event listeners for click to determine which elements are clickable, as touch gestures are not exact (a finger is actually an ellipsis, i.e. a fat pointer). See here
    Note: The reference above suggests this is not an issue for touchstart as it always uses the center of the finger...

Performance benchmark (#8432)

This benchmark compares ngClick to a possible event delegation directive. It measures how long it takes to instantiate directives.

Results (given the current master of AngularJS 1.x and in Chrome M37):

  • ngClick is very close to ngShow and text interpolation, especially when looking at a version of ngClick that does not use jqLite.on but element.addEventListener directly
  • A delegate event directive that has no link function has the same speed as a directive with a link function. I.e. ngClick is slower compared to the delegate event directive only because ngClick touches the DOM for every element
  • A delegate event directive could be about 2x faster than ngClick. However, the overall performance
    of a use case depends on other factors as well, e.g. how many (and which) other directives are used and what other things are going on (e.g. compiling a template, ...).

Besides the micro benchmarks we also tried delegation event directives in one of our big AngularJS apps to reduce startup time and save memory. However, in our tests it did not make a huge difference there as well.

Memory benchmark

Removing the event handler closure in ngClick is 130 Bytes (40000 listeners == 2.5MB).
Given the master of AngularJS 1.x and in Chrome M37.

Conclusion

We will not implement event delegation in AngularJS 1.x as there would be a lot of special cases, which also depend on other libraries that are used, and the speed and memory impact is not big enough for this. E.g. the click problem in mobile safari would result in no performance gain there, as ngClick would need to install those empty event listeners. However, when Fastclick is used, we don't need those listeners as Fastclick patches the DOM prototypes to use touchstart and touchend under the hoods, which can lead to jank as they would be installed on the document due to event delegation.

However, for Angular 2.0 we will revisit this topic.

Please let me know if I missed something important.

Please also let me know if you have a good real world use case for which you did performance tests that show that an event delegation directive in AngularJS would have a big impact.

/cc @IgorMinar

All 18 comments

there would be a nice performance upgrade

I'm not convinced that this would yield better performance in practice. In your example, there would be extra work involved if you listen to determine which element was actually clicked and evaluate the expression on that scope. I'd love to be proven wrong, though. Do you have some benchmarks that suggest that this is performance-critical?

If you include jQuery on the page, AngularJS will automatically use that instead of JQLite, giving you element.delegate and element.live. You can use this to test your theory.

I've done a lot of work building my own AJAX websites and the major speed boost I got was when I changed click handlers down to delegated events that are attached onto the document.body element. So let me first build a comparison test.

In regards to JQuery being included:

  1. Not everyone uses JQuery as their primary JavaScript framework.
  2. If AngularJS decides on using element.delegate and element.live then it would have to be in it's core and not only offered as a 3rd party feature.

Iet me first make a comparison of the two and then we'll see what we can do from there.

Hi matsko. Did you make the comparison? I'm looking forward for it.

I am curious what y'all think of this delegate directive?

http://www.bennadel.com/blog/2448-Using-jQuery-Event-Delegation-In-AngularJS.htm

Would be interesting to see a ng-delegate-click ... or if I am missing something where angular doesn't see perf issues with event delegation, I am very interested in hearing/reading about that.

I actually discovered that $rootElement works already as a big delegator for click events. So any a elements will get caught up by the click event on the root element, but ngClick listens per element. So I actually think that this is good as it is, because if you have everything captured on one element and ngClick is still around then the developer has to capture the ngClick event before the global aLink event is caught--which is messy. But I do think we should add element.delegate to the core.

+1 for this issue.

@btford, If you are looking for any comparisons between bind and delegate then I have created a Bind vs On vs Delegate test on jsperf. As you can see, delegate is the clear winner here and this margin increases as number of DOM elements are increased.

_Note - Before creating this test, I was not aware that latest jQuery eventually invokes on for both delegate and bind. But I guess it does not matter here as jQuery Lite used in angular does not have implementation of on_.

master branch does contain an implementation of on in jqLite

On 17 July 2013 14:38, Nihar Sawant [email protected] wrote:

+1 for this issue.

If you are looking for any comparisons between bind and delegate then I
have created a Bind vs On vs Delegatehttp://jsperf.com/bind-vs-on-vs-delegatetest on jsperf. As you can see,
delegate is the clear winner here and this margin increases as number of
DOM elements are increased.

_Note - Before creating this test, I was not aware that latest jQuery
eventually invokes on for both delegate and bind. But I guess it does not
matter here as jQuery Lite used in angular does not have implementation of
on_.

—
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/1571#issuecomment-21113129
.

Can you be more specific about the version? I am using 1.0.7. I want to switch to 1.1.5 (as it has ng-animation) but being unstable I am not implementing it. Any idea when 1.1.5 will go stable?

It was landed in this commit:
https://github.com/angular/angular.js/commit/f1b94b4b599ab701bc75b55bbbbb73c5ef329a93
on
19 June. This is after the most recent release of 1.1.5. We are pushing
hard to release 1.2 very soon.

On 17 July 2013 14:55, Nihar Sawant [email protected] wrote:

Can you be more specific about the version? I am using 1.0.7. I want to
switch to 1.1.5 (as it has ng-animation) but being unstable I am not
implementing it. Any idea when 1.1.5 will go stable?

—
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/1571#issuecomment-21114242
.

Does not support namespaces, selectors or eventData :worried: If selectors are not supported then event delegation won't work, isn't it?

Yes you are right.

On 17 July 2013 15:10, Nihar Sawant [email protected] wrote:

_Does not support namespaces, selectors or eventData_ [image: :worried:]If selectors are not supported then event delegation won't work, isn't it?

—
Reply to this email directly or view it on GitHubhttps://github.com/angular/angular.js/issues/1571#issuecomment-21115275
.

Is there any particular reason why on is implemented partially? Otherwise I will start looking for alternatives.

@dewbot a comparison in a vacuum doesn't tell us anything. Using event delegation may introduce additional work that may be more expensive than whatever event delegation affords us. You need to benchmark it in the context of ngClick to know if it's worth it.

Hi @btford,

Sorry for the late reply. For last couple of days, I have been trying to create a new test in jsPerf for the comparison between bind, delegate and ng-click but unfortunately I can't create a proper one. Can you provide me some ng-click based existing tests if you are aware of any? Or if it is possible, can you take some time off to create new revision of the test I made? Or can you suggest me any other ways to do performance testing with (like using DevTools)?

Thanks

@btford anything we can do about this?

@tbosch has a branch that adds event delegation to all ng-* directives. https://github.com/tbosch/angular.js/compare/delegate-events it's still a bit rough, but if someone would like to evaluate it on a real app then please do and post feedback here. (we are primarily interested to know if it has perf/memory impact)

if we are going to do this we should also add event delegation to a directive: https://github.com/angular/angular.js/blob/75345e3487642fbc608c3673e64cd7c5d65cb386/src/ng/directive/a.js#L37 (that should be a separate PR though)

@matsko you said that $rootElement already works as event delegator, but that's only for link rewriting.

additionally there are other events that should be delegated that were not mentioned here. primarily all the events in forms (input[*] directives)

@IgorMinar yes. $rootElement handles the click operations for all children on itself and then makes the connection to the clicked element. But this is very coupled into $location so you can't use it on your own.

Hi,
I investigated event delegation for AngularJS with the following results and benchmarks:

Possible directives that could use event delegation

  • ngClick, ...: directives that evaluate an expression when an event happens
  • $location: Already implements event delegation using the click event (see here).
  • a[href]: Has a link function to prevent clicks on empty links (see here). The link function could be removed with event delegation in $location.
  • ngModel: the link function of the directive cannot be removed, only the installation of the event listener.

Possible benefits

  • performance: No need to set up an event listener per element, and also no need to call the link function of the event directive
  • memory: Less memory consumption because of less closures

Possible problems

  • Installing a touchstart event listener on document can cause jank during scrolling (see here).
    Reason: The browser needs to call the touchstart handler to see whether it event.preventDefault()ed before it can start scrolling. Same problem when using gesture events from a library like ngTouch or hammer.js: Installing a gesture listener for e.g. tap on the document requires the library to install a touchstart event listener on the document as well
  • mobile Safari: for click event to bubble up there needs to be at least one click handler directly on the element (although the handler can be empty). E.g. el.addEventLisener('click', function() {}, false). See here.
    Note: the real click event is probably not very important for mobile apps as they want to use the touchxxx events and a gesture library like ngTouch or hammer.js to get faster feedback to user input (e.g. no 300ms delay)
  • Some events don't bubble, e.g. blur and focus
    Workaround: could have a black list or use capturing event listeners
  • Does not work well with ShadowDOM as not all events bubble through the ShadowDOM boundary, see here. E.g. change event is stopped.
    Note: This is not an issue for AngularJS 1.x, but will be important for AngularJS 2.x
  • Mobile browsers use installed event listeners for click to determine which elements are clickable, as touch gestures are not exact (a finger is actually an ellipsis, i.e. a fat pointer). See here
    Note: The reference above suggests this is not an issue for touchstart as it always uses the center of the finger...

Performance benchmark (#8432)

This benchmark compares ngClick to a possible event delegation directive. It measures how long it takes to instantiate directives.

Results (given the current master of AngularJS 1.x and in Chrome M37):

  • ngClick is very close to ngShow and text interpolation, especially when looking at a version of ngClick that does not use jqLite.on but element.addEventListener directly
  • A delegate event directive that has no link function has the same speed as a directive with a link function. I.e. ngClick is slower compared to the delegate event directive only because ngClick touches the DOM for every element
  • A delegate event directive could be about 2x faster than ngClick. However, the overall performance
    of a use case depends on other factors as well, e.g. how many (and which) other directives are used and what other things are going on (e.g. compiling a template, ...).

Besides the micro benchmarks we also tried delegation event directives in one of our big AngularJS apps to reduce startup time and save memory. However, in our tests it did not make a huge difference there as well.

Memory benchmark

Removing the event handler closure in ngClick is 130 Bytes (40000 listeners == 2.5MB).
Given the master of AngularJS 1.x and in Chrome M37.

Conclusion

We will not implement event delegation in AngularJS 1.x as there would be a lot of special cases, which also depend on other libraries that are used, and the speed and memory impact is not big enough for this. E.g. the click problem in mobile safari would result in no performance gain there, as ngClick would need to install those empty event listeners. However, when Fastclick is used, we don't need those listeners as Fastclick patches the DOM prototypes to use touchstart and touchend under the hoods, which can lead to jank as they would be installed on the document due to event delegation.

However, for Angular 2.0 we will revisit this topic.

Please let me know if I missed something important.

Please also let me know if you have a good real world use case for which you did performance tests that show that an event delegation directive in AngularJS would have a big impact.

/cc @IgorMinar

Was this page helpful?
0 / 5 - 0 ratings