-
Notifications
You must be signed in to change notification settings - Fork 27.5k
$watchCollection newValue always == oldValue #2621
Comments
I noticed this too when trying to iterate over the oldCollection to remove Google Maps markers. |
Reduction: |
I think you mean "reproduction". I legitimately thought maybe you were talking about a "reduce" function at first ;) |
Ah I see. I've always just called them test cases, examples or even proofs. I still stand by the fact that the language is ambiguous given that "reduce" is a common enough term general programming. Thanks for the test case/example/proof though! ;) |
$watchCollection seems to be exceptionally flaky. Not only is there this unbelievably glaring issue (really, I'm not sure how a bug this big makes it to release at a place like Google), but in my limited testing, it seems to be behaving more like a deep watch than a shallow one. It's not my codebase, so I don't yet know if there's something particularly funky in the app, but I'm getting watchCollection calls just by changing the details of objects nested within the array I'm watching. |
There isn't much documentation on $watchCollection so the intent is a bit ambiguous. On first thought, I'd say that bubbling child events through the parent collection makes sense. That said, how do you only get it to watch the collection itself? |
If you want to deep watch, you can $scope.$watch(..., true). For instance:
'quantity': 1}];
listenerB and listenerC fire.
property on one of the items in the cart has changed. Only listenerB fires. On Fri, May 31, 2013 at 1:12 AM, Soviut notifications@github.com wrote: — |
Here's a new test for this: |
I took the naive approach suggested in angular#2621 of separating out the changeDetected logic from the rest of the watch to give the listener time to execute before oldCollection is overwritten. The logic should be identical to what's in the main repo, except no changes are made to the closed-over state until after listener is called back. Unfortunately, this doesn't appear to work, as the watchCollection tests are now all failing. I need to learn more about Karma so I can better debug this, and take more time to grok the logic of the function before I'd have a shot at fixing it.
@mattlanders I followed your advice and moved all the side-effects from watchCollectionWatch into watchCollectionAction, leaving only the change detecting logic in watchCollectionWatch. Unfortunately, that doesn't seem to work. (The watchCollection tests are all failing now.) I'd have to spend some more time reading and debugging this before I'd be able to submit a solid pull request. |
Alright. I'm getting close. Here's the work so far. All the
And here is the error causing those failures:
The fixed version of |
Also, I added a note to the docs about newCollection and oldCollection being of different types. It would be a better choice if newCollection and oldCollection were always the same type (in this case, a POJO or an Array), but that has the potential to be a breaking change. |
I made a note in ticket angular#2621 about the type mismatch.
Don't know if someone will need this, but it seems that this $watchCollection bug still present in future 1.1.6 release. As a way to temporary bypass this bug one could simply use the 'normal' $watch function like this: $scope.model.cart = [1,2,3];
function getArray(){
return $scope.model.cart;
}
function doSomething(newValues, oldValues){
//...
}
$scope.$watch(getArray, doSomething, true); //deep watch |
It's not doing a deep compare. It checks references for each high-level key (object) or index (array). The problem is that it makes a shallow copy from newValue to oldValue, so when the watch function returns, newValue = oldValue, kind of.
|
Hi |
Unfortunately this issue is still present in Angular 1.2.3. Would love to see a fix. Heads up :) |
+1 |
1.2.5, still a bug. http://plnkr.co/edit/rfMCF4x6CmVVT957DPSS?p=preview |
👍 |
1 similar comment
+1 |
I'm curious... do you really want it to make a new copy of the collection just for the listener function? $watchCollection keeps a copy anyways, but only so it can spot differences. Seems like it would be a fair bit more expensive to be cloning the value every digest |
I think at the very least, the API and docs should be updated then, because it is extremely misleading. People will implement behaviour based on the assumption that $watchCollection does what it says in the docs, and that it's listener receives a newCollection and an oldCollection. The documentation explicitly states:
This is clearly not the case, and hasn't been the case for a very long time, if ever. |
+1 Can I be of any help? |
+1 |
+1 |
2 similar comments
+1 |
+1 |
+1 |
+1 |
1 similar comment
+1 |
Originally we destroyed the oldValue by incrementaly copying over portions of the newValue into the oldValue during dirty-checking, this resulted in oldValue to be equal to newValue by the time we called the watchCollection listener. The fix creates a copy of the newValue each time a change is detected and then uses that copy *the next time* a change is detected. To make `$watchCollection` behave the same way as `$watch`, during the first iteration the listener is called with newValue and oldValue being identical. Since many of the corner-cases are already covered by existing tests, I refactored the test logging to include oldValue and made the tests more readable. Closes angular#2621 Closes angular#5661 Closes angular#5688
@IgorMinar - thank you for the fix. could you post in what release you think it will be rolled out? |
Originally we destroyed the oldValue by incrementaly copying over portions of the newValue into the oldValue during dirty-checking, this resulted in oldValue to be equal to newValue by the time we called the watchCollection listener. The fix creates a copy of the newValue each time a change is detected and then uses that copy *the next time* a change is detected. To make `$watchCollection` behave the same way as `$watch`, during the first iteration the listener is called with newValue and oldValue being identical. Since many of the corner-cases are already covered by existing tests, I refactored the test logging to include oldValue and made the tests more readable. Closes angular#2621 Closes angular#5661 Closes angular#5688 Closes angular#6736
Originally we destroyed the oldValue by incrementaly copying over portions of the newValue into the oldValue during dirty-checking, this resulted in oldValue to be equal to newValue by the time we called the watchCollection listener. The fix creates a copy of the newValue each time a change is detected and then uses that copy *the next time* a change is detected. To make `$watchCollection` behave the same way as `$watch`, during the first iteration the listener is called with newValue and oldValue being identical. Since many of the corner-cases are already covered by existing tests, I refactored the test logging to include oldValue and made the tests more readable. Closes #2621 Closes #5661 Closes #5688 Closes #6736
beta3 and 1.2.15, it's already out as a prerelease via bower or On Tue Mar 18 2014 at 9:11:37 AM, Nikita Tovstoles notifications@github.com
|
Still at first firing of callback, old and new value are the same.. (instead old value being empty array) Following helps:
|
Adam, $watch has the same behaviour. It's intentional. John M On 25 April 2014 22:04, Adam Stankiewicz notifications@github.com wrote:
|
@johnsoftek that actually depends... It will have the same new and old value if the value changes in the same turn that the listener is registered, but if the listener is registered in one turn and then the value changes, the listener will fire with undefined for the old value. I would question whether this is "intentional" or not, really. In watchtower we certainly don't intend for that behaviour |
+1 |
newValue = oldValue ??? Why is then the watch triggered?? |
@mato75 look at the API for |
+1 |
@caitp $watch always calls the listener in the first digest after a watch is registered. It has a special check for first time that ensures it always return newvalue === oldvalue |
@IgorMinar, your comment is confusing me. Was this fixed in |
Very interesting thing I've observed just now. Don't fully understand the triggering conditions, but I was under the wrong impression that |
I trying to use the $watchCollection to watch an array, and I noticed that the newvalue is always equal to the oldvalue. After looking at the code, it looks like the values are copied to oldValue prior to firing the listener. It should be an easy fix.
The text was updated successfully, but these errors were encountered: