Tonight, I hope to solve a minor mystery with data binding in the Dart flavor of Polymer. I have a workaround, but the problem surprised me enough that it seems worth investigating.
Things went wonky for me when I tried binding the
labels
property of the <hello-you>
Polymer to the labels
attribute of the <polymer-translate>
Polymer:<polymer-element name="hello-you" attributes="locale"> <template> <polymer-translate locale={{locale}} labels="{{labels}}"></polymer-translate> </template> </polymer-element>The idea of doing this is to provide a vehicle through which the
<polymer-translate>
Polymer can communicate labels to apply to <hello-you>
for the current locale:And it works. To a point.
What does not work—at least not without a workaround—is watching the
labels
attribute for changes inside <polymer-translate>
:@CustomTag('polymer-translate-custom') class PolymerTranslate extends PolymerElement { @published Map labels = toObservable({}); // ... ready() { super.ready(); labels.changes.listen((list){ print('[polymer_translate] $list'); // Never hit here :( }); } }Since
<polymer-translate>
is the thing changing the labels
(e.g. when the locale
changes), it might seem a little strange that I want to watch for changes in there as well. In all honesty, my reasons in this case are not as pure as they should be.It feels like changes to bound variables in Polymer really want to be unidirectional (e.g.
<polymer-translate>
pushing changes to <hello-you>
). Whether that is right is open for debate, but I am already violating this rule by pushing a change in the opposite direction. I am pushing a single attribute change from <hello-you>
to <polymer-translate>
—a number that will affect a pluralization translation. When that occurs I want <polymer-tranlate>
to see the change, and react by updating the other labels
attributes accordingly.But
<polymer-translate>
never sees the change. Unless…Unless I wait a single browser event loop before establishing that listener:
@CustomTag('polymer-translate-custom') class PolymerTranslate extends PolymerElement { @published Map labels = toObservable({}); // ... ready() { super.ready(); Timer.run((){ labels.changes.listen((list) { print('[polymer_translate] $list'); // Now we hit here :) update(); }); }); } // ... }When I observe the same thing in the JavaScript version of this code, it works. Of course, the JavaScript version has
observe
blocks:Polymer('polymer-translate-custom', { labels: {}, // ... observe: { 'labels.__count__': 'translate' }, // ... });Wait a second… JavaScript. I remember something about object literals and shared state in the JavaScript flavor of Polymer… Dang it. I'm an idiot.
I should expect this kind of behavior when binding variables—in either Dart or JavaScript. When I bind
<hello-you>
's locale
property to <polymer-translate>
's locale
attribute, I am replacing the initial value in <polymer-translate>
with a reference to the object in <hello-you>
.When
<polymer-translate>
is ready()
, it still thinks locale
is the original value. And then <hello-you>
immediately proceeds to replace it with what it thinks locale
should be. And I wind up listening for changes on nothing. By waiting for one browser event loop, I give <hello-you>
a chance to make that assignment, and thus my code works. Now that I understand this, I realize that, instead of doing this when
<polymer-translate>
is ready()
, I should do it when <polymer-translate>
has enteredView()
(or it is attached now?):@CustomTag('polymer-translate') class PolymerTranslate extends PolymerElement { @published Map labels; // ... enteredView() { super.enteredView(); labels.changes.listen((list) { print('[polymer_translate] $list'); update(); }); } // ... }It still seems to be called
enteredView()
in the version (0.9.5) that I have, but I really need to upgrade Dart as well as Polymer. Regardless, this works now. Once <polymer-translate>
has entered the view—of <hello-you>
—the appropriate labels
are set and everything works just fine without the need to wait.Now that I think about it, this makes complete sense. I have a hard time even recalling why I would have thought otherwise. When I assign an attribute—be it in a template or directly—I replace the original value with the newly assigned value. Not only is the value different, but any associated properties or event listeners are also different.
Sometimes it just helps to take a step back to figure these things out. Other times, it helps to make the mistake in full, public view after struggling with it for a day. Hopefully the latter results in actual learning and remembering!
Day #1,040