emberjs/ember.js — Issue #460: Two way transform bindings can trigger too often due to object equality
Author: robmonie
Created: 2012-01-29 00:04:02+00:00
State: open

Due to the way object equality works in javascript, it seems that there's no way to do a two way transform between non-primitive values without excessive binding propagation. This results in situations where observers can be fired more times than expected.

Take the following jsfiddle example: http://jsfiddle.net/SZnbL/5/

Here we have two Ember objects. One has a two way binding to a property on another, with a transform between a date value at one end (the controller) and a string value at the other (the view). When a date is set on the controller, the value is propagated to the view and transformed to a string. Because the value is considered as changed in the view, the binding then pushes the value back to the controller. This goes back via the transform which converts the string back to a date resulting in a new date object. Because the object will always be different when transformed back to a date (unless dates are cached which has it's own issues) and there is no way to override == for a type in js, the binding will consider this to be a new value for the date in the controller and will both trigger the observer and fire the binding back to the view, converting it to a string again. Because the date string in the view is now considered == , the binding will settle.

This isn't all bad as it does eventually settle after doing a bit more work than you'd hope, but if you have an observer on the date property, it will be fired twice instead of once which is not expected behaviour. I haven't written a test for this but i'd imagine if you didn't have a primitive somewhere in the mix, the binding would go on forever.

One possible way of overcoming this could be to optionally allow object.equals(other) to be implemented on objects that need to be compared via some other form of equality. This would come at a small price in w_set though so maybe there's a better way.

It is also possible to get around this on a case by case basis, or in this case, comparing before and after values in the observer. For this it would be nice if observers were passed the previous value to make it easier.

I'll be off on leave for three weeks so in case anyone comments on this, i've copied @keithpitt into this as we discovered this while pairing last week.