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

fix(input): prevent double $digest when using jQuery trigger. #5293

Closed
wants to merge 1 commit into from
Closed

fix(input): prevent double $digest when using jQuery trigger. #5293

wants to merge 1 commit into from

Conversation

mgol
Copy link
Member

@mgol mgol commented Dec 5, 2013

If an event was performed natively, jQuery sets the isTrigger property.
When triggering event manually, the field is not present. Manually
triggered events are performed synchronously which causes the "$digest
already in progress" error.

@mary-poppins
Copy link

Thanks for the PR!

  • Contributor signed CLA now or in the past
    • If you just signed, leave a comment here with your real name
  • PR's commit messages follow the commit message format

If you need to make changes to your pull request, you can update the commit with git commit --amend.
Then, update the pull request with git push -f.

Thanks again for your help!

@@ -515,6 +515,24 @@ describe('input', function() {
'event so that form auto complete works',function() {
assertBrowserSupportsChangeEvent(true);
});

if (jqLite().jquery) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Is this how you detect jQuery in tests? Should I do it differently?

Copy link
Contributor

Choose a reason for hiding this comment

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

There seems to be a variety of ways!!

if(!_jqLiteMode)
if (!jqLite.fn)
if (window.jQuery)

I guess the first is the right method for internal tests

@petebacondarwin
Copy link
Contributor

This seems like a more general problem with JQuery.trigger, which should be dealt with outside of input...

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

@petebacondarwin Hmm, maybe it would be better to monkey-patch jQuery.fn.trigger by wrapping its body in setTimeout. I'll do it tomorrow if that's fine by you.

The test should be fine, though. :)

@caitp
Copy link
Contributor

caitp commented Dec 5, 2013

maybe it would be better to monkey-patch

There has got to be a better way

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

@caitp

maybe it would be better to monkey-patch

There has got to be a better way


Than monkey-patching? I don't think so. Handlers can't be modified for various reasons and you have to handle both situations with native event dispatch and ones with jQuery.fn.trigger.

@caitp
Copy link
Contributor

caitp commented Dec 5, 2013

The problem is that it becomes really, really hard to test this, as people are using different versions of jQuery, and by monkey-patching you end up having to essentially support all of them seperately, it seems like an absolute nightmare.

If there is any other solution at all, it would be the one to use :|

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

@caitp I agree it would be better not to monkey-patch, though Angular already monkey-patches a couple of key jQuery methods.

On a further thought, this change could be more disrupting as changing synchronous function to an asymchronous one is a serious thing and such a change could break a LOT of code.

The only other solution I can think of is to go through the Angular code base and add such guards as I added in this PR to every single event handler that changes sth in scope. Such a policy would have to be strictly followed in the future. For handlers defined in user code the user would have to care to make similar amendments to all handlers influencing scope.

I prefer the second approach, I agree making trigger async can be an extremely dangerous thing to do.

Thoughts?

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

(the policy I described would go away as soon as Angular drops $digest but not before that happens)

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

Another option would be to monkey-patch 'on' so that it wraps all handlers in a conditional scope.$apply depending on event.isTrigget value. This would break almost every current code (it would require people to remove their scope.$apply wrapper around handlers bodies), though so that wouldn't be an option for now and it still would be dangerous.

I don't see a better option than to extend what current PR does to other places in the code base.

@caitp
Copy link
Contributor

caitp commented Dec 5, 2013

no, monkey-patching 'on' or 'bind' or anything else like that is not appropriate behaviour either...

I'm not sure what the right thing to do here is, but I don't think monkey-patching jQuery is the right thing to do, just because there's no guarantee that we won't break peoples apps who happen to be using some version of JQ which has slightly different behaviour. I don't know specifically how stable that particular property's behaviour is, but we can't really test for every canonical version of jQuery ever released, and it's not good to say "we only support 2.0.3" or something (because people are definitely using older versions for IE7 compatibility, among other reasons)

These are just my personal thoughts -- One of the core team might have a different opinion on the matter

@mgol
Copy link
Member Author

mgol commented Dec 5, 2013

@caitp You didn't comment on my preferred solution that involves no additional monkey-patching. :)

@caitp
Copy link
Contributor

caitp commented Dec 5, 2013

I think my comment was that "I'm not sure what to do about it"...

It would be good to come up with something, because clearly it's a problem (although not one I've ever seen with 1.2.3 + jquery 2.0.3)

But monkey-patching external code of which many versions are in use, seems like a terrible idea to me.

@ghost ghost assigned jeffbcross Dec 9, 2013
If an event was performed natively, jQuery sets the isTrigger property.
When triggering event manually, the field is not present. Manually
triggered events are performed synchronously which causes the "$digest
already in progress" error.
@sicarrots
Copy link

+1

1 similar comment
@jerzydem
Copy link

+1

@ghost ghost assigned IgorMinar Dec 30, 2013
@IgorMinar
Copy link
Contributor

lgtm. thanks for iterating on this patch @mzgol and @caitp

@IgorMinar IgorMinar closed this in 1147f21 Dec 30, 2013
@caitp
Copy link
Contributor

caitp commented Dec 30, 2013

Erm, I realize I said earlier today that it looked like my comments were addressed re: not monkey patching, there are some things that come up on closer inspection, which might be good to lookat as a followup patch if you're interested @mzgol.

Primarily, the test... 2 things. One, it might be good to put together an e2e test so that we can make sure that this works correctly with native triggers. 2, the spec could be cleared up a lot. It appears that you're expecting the test not to throw, but this isn't explicitly done anywhere in the spec, and there seems to be a lot of unrelated stuff happening as well. That should probably be cleaned up.

If you want to take care of that and submit a followup that would be awesome, or I can take a stab at it. I don't think we have a very good way to set up e2e tests apart from the docs e2e stuff right now, though.

@IgorMinar
Copy link
Contributor

This merged commit us breaking apps at Google. We need to investigate this and we might need to revert this change.

@IgorMinar IgorMinar reopened this Dec 31, 2013
@caitp
Copy link
Contributor

caitp commented Dec 31, 2013

Yes, sorry for giving the impression that this was totally merge-able, I really only meant that my comments about monkey-patching had been addressed. It would be super-helpful to get some e2e tests into this patch so that we can be sure it doesn't break things.

@IgorMinar
Copy link
Contributor

The issue is that the assumption that all manually triggered change events triggered from within an apply is invalid.

It is possible to have a jquery plugin trigger a change event that ng-model listens on and in this case the view value change will go unobserved until the next digest.

@IgorMinar IgorMinar closed this in a80049f Dec 31, 2013
@IgorMinar
Copy link
Contributor

I fixed the issue by checking $$phase which is super ugly but I can't think of a better solution right now. Let's try this and if it doesn't work, we'll fix it again in some other way.

@mgol mgol deleted the jquery-trigger branch January 2, 2014 09:01
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
If an event was performed natively, jQuery sets the isTrigger property.
When triggering event manually, the field is not present. Manually
triggered events are performed synchronously which causes the "$digest
already in progress" error.

Closes angular#5293
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
If an event was performed natively, jQuery sets the isTrigger property.
When triggering event manually, the field is not present. Manually
triggered events are performed synchronously which causes the "$digest
already in progress" error.

Closes angular#5293
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
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.

8 participants