Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngEventDirs): check for $rootScope.$$phase in event handler and d… #14674

Closed
wants to merge 1 commit into from
Closed

fix(ngEventDirs): check for $rootScope.$$phase in event handler and d… #14674

wants to merge 1 commit into from

Conversation

markgardner
Copy link
Contributor

@markgardner markgardner commented May 25, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Bug fix

What is the current behavior? (You can also link to an open issue here)
#14673

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

What is the new behavior (if this is a feature change)?
This will change ngClick and the like to check for $rootScope.$$phase to determine if it should call the callback function without entering a new $apply.

Does this PR introduce a breaking change?
Nope

Please check if the PR fulfills these requirements

Other information:

@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.

1 similar comment
@googlebot
Copy link

We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.

@googlebot
Copy link

CLAs look good, thanks!

1 similar comment
@googlebot
Copy link

CLAs look good, thanks!

@@ -65,6 +65,8 @@ forEach(
};
if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
scope.$evalAsync(callback);
} else if ($rootScope.$$phase) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to avoid double checking $rootScope.$$phase and simplify, I might change the whole block to:

if (!$rootScope.$$phase) {
  scope.$apply(callback);
} else if (forceAsyncEvents[eventName]) {
  scope.$evalAsync(callback);
} else {
  callback();
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point, I've updated the commit to reflect this.

@gkalpak
Copy link
Member

gkalpak commented May 25, 2016

Technically speaking, there are ways to avoid this (e.g. wrapping the call to click() in a $timeout or preventing event propagation) and having an ngClick handler on an element that calls .click() on a sibling element, while their parent element also has an ngClick handler isn't a very common usecase 😃

But then again, why not ? It has minimal overhead and is a reasonable change 👍

LGTM

@markgardner
Copy link
Contributor Author

@gkalpak very true, I have had this happen a couple of times and normally worked around it.

@jbedard
Copy link
Collaborator

jbedard commented May 25, 2016

I think the reason this was never done is because it makes element.click() unpredictable. With this change the event handler might be async or might not. If it is async then things like event.preventDefault() or event.stopPropagation() won't work, event order might become unpredictable etc. Blur and focus were special cases because they are sometimes automatically triggered by the browser when manipulating the dom (at least blur is).

@markgardner
Copy link
Contributor Author

@jbedard that makes sense that would be a concern. Though I don't think this change will effect that. The biggest difference is whether it will use $rootScope.$apply or a regular function call to call the event handler. Both should be synchronous if I understand the code correctly.

@jbedard
Copy link
Collaborator

jbedard commented May 26, 2016

My bad then! I guess it's only the forceAsyncEvents have that behavior and this doesn't change that. I wonder if it is worth moving the expect($rootScope.click).toHaveBeenCalledOnce() into the $apply to verify this?

@gkalpak
Copy link
Member

gkalpak commented May 26, 2016

Previously if an non-async ng-event was fired while a $digest was in progress, there would be an error. With this PR, such calls will execute the callback directly (without $apply()).

So, the only change in terms of behavior is that calls that would previously throw, now won't.

Moving the expectation inside the $apply block won't hurt, but it doesn't buy us anything either. It only verifies half of the story (events fired while $digest in progress) - it doesn't tell us anything about events fired outside of a $digest.

I would be fine adding adding another test that verifies the synchronous nature of both types of callback calls (events fired inside and outside of $digest).

@markgardner
Copy link
Contributor Author

I've updated the PR to include another test to cover triggering an event outside of a digest cycle.

element.triggerHandler('click');
});

expect($rootScope.click).toHaveBeenCalledOnce();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually check that it is called synchronously (e.g. it might be called outside of the $apply() block).
You could use the same approach as below (using a $watch).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the PR to use the same checks for both tests

…on't $apply if already in $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes #14673
@Narretz Narretz self-assigned this Jul 12, 2018
Narretz pushed a commit to Narretz/angular.js that referenced this pull request Jul 13, 2018
…on't $apply if already in $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes angular#14673

Closes angular#14674
Narretz pushed a commit to Narretz/angular.js that referenced this pull request Jul 23, 2018
…on't $apply if already in $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes angular#14673
Closes angular#14674
Narretz pushed a commit to Narretz/angular.js that referenced this pull request Jul 23, 2018
…n $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes angular#14673
Closes angular#14674
Narretz pushed a commit to Narretz/angular.js that referenced this pull request Jul 23, 2018
…n $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes angular#14673
Closes angular#14674
@Narretz Narretz closed this in 6b0193e Jul 25, 2018
Narretz pushed a commit that referenced this pull request Jul 25, 2018
…n $digest

Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes #14673
Closes #14674
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants