-
Notifications
You must be signed in to change notification settings - Fork 27.5k
moment.js and scope.$watch when objectEquality is true #4996
Comments
It's not the angular equals function which is causing infinite digests --- these will happen when the actual value being compared changes too many times per digest cycle (sometimes called "model instability") An example can be seen at http://plnkr.co/edit/AFnZYW403P4SN2xAvkWw?p=preview which intentionally modifies the value every time the $watch fires --- commenting out one of the two assignments in the $watch function will prevent from throwing. |
ok, but look at this example (http://plnkr.co/edit/rYxlCM?p=preview) no assignments happens in the $watch function and still throwing an error. |
Workaround without objectEquality: http://plnkr.co/edit/MjFelt?p=preview |
No, interpolated expressions in text nodes are actually separate $watches --- In the case of this one in particular, it is actually executing an expression which evaluates to a new object every single time it is called. This, quite understandably, causes an infdig error to be thrown. The first $watch in your controller does not cause any problems, it is the {{moment.format(...)}} which does. A good work-around for your issue is to write a filter for your momentjs needs, it's a very simple thing to do. angular.module("moment", [])
.filter("time", function() {
return function(input) {
if (input instanceof Date) {
return moment(input).format('hh:mm:ss')
}
}
}); <p>{{ theDateInQuestion | time }}</p> It's a pattern which works nicely |
As stated, you can use filters, this is really what they're meant to do --- transform input in some fashion (such as formatting a date). The filter chain won't cause an infdig error, and can be used very cleanly in any angular expression. They're good things to use |
ok! thanks |
@caitp Are you sure about that? Removing the binding from the view still gives errors. I think the real reason is that angular objectEquality check doesn't work for moment object. |
Moment objects are nothing special, they are just objects (Which happen to contain a Date, which is treated specially by The cause of You can see filters used to circumvent this in both of the examples I've posted. Here's another one: http://plnkr.co/edit/MjNWFI?p=preview Hmm, actually I retract that statement, it seems to end up with newval being a simple object, and oldval being the actual Moment instance --- So perhaps $watch does have issues with new'd objects. Regardless, there are certainly ways to work around it (one being, simply store a Date object rather than a Moment) EXCEPT, the first example uses the objectEquality flag watching a Moment object, and does not suffer from this issue: http://plnkr.co/edit/AFnZYW403P4SN2xAvkWw?p=preview So, maybe I need to study your example closer. At any rate, there are ways around this which don't require silly hacks. Use filters! It's what they're there for. |
Here's you last referenced example, with the only change that the moment instance is stored in This happens even if you don't even use the moment attribute in your template... it's purely an issue with angular's copy() and equals() behavior with MomentJS. The root of this, it seems, is that angular.copy() does not preserve the objects' prototype: #5063 Thanks! |
I agree |
I am waiting for instructions if I should make #5063 backward-compatible with branch 1.2.x (and with IE8). |
So far, angular.copy was copying all properties including those from prototype chain and was losing the whole prototype chain (except for Date, Regexp, and Array). Deep copy should exclude properties from the prototype chain because it is useless to do so. When modified, properties from prototype chain are overwritten on the object itself and will be deeply copied then. Moreover, preserving prototype chain allows instanceof operator to be consistent between the source object and the copy. Before this change, var Foo = function() {}; var foo = new Foo(); var fooCopy = angular.copy(foo); foo instanceof Foo; // => true fooCopy instanceof Foo; // => false Now, foo instanceof Foo; // => true fooCopy instanceof Foo; // => true The new behaviour is useful when using $http transformResponse. When receiving JSON data, we could transform it and instantiate real object "types" from it. The transformed response is always copied by Angular. The old behaviour was losing the whole prototype chain and broke all "types" from third-party libraries depending on instanceof. Closes angular#5063 Closes angular#3767 Closes angular#4996 BREAKING CHANGE: This changes `angular.copy` so that it applies the prototype of the original object to the copied object. Previously, `angular.copy` would copy properties of the original object's prototype chain directly onto the copied object. This means that if you iterate over only the copied object's `hasOwnProperty` properties, it will no longer contain the properties from the prototype. This is actually much more reasonable behaviour and it is unlikely that applications are actually relying on this. If this behaviour is relied upon, in an app, then one should simply iterate over all the properties on the object (and its inherited properties) and not filter them with `hasOwnProperty`. **Be aware that this change also uses a feature that is not compatible with IE8.** If you need this to work on IE8 then you would need to provide a polyfill for `Object.create` and `Object.getPrototypeOf`.
backported commit: angular@b59b04f which is a fix to: angular#4996
Hi,
I have a object that have a moment.js property like this:
so if i watch this object with objectEquality like this:
i get this errors:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
To fix this i need to override angular.equals function to compare momentjs objects correctly.
What is the best way to do this?
thanks.
The text was updated successfully, but these errors were encountered: