Wednesday, August 6, 2014

I Know What ComputedProperty Does!


After two night's of experimentation, I swear to you that the @ComputedProperty annotation in Polymer.dart serves no purpose. No purpose whatsoever!

Well, OK, it does something, but as far as my experiments have determined, it does exactly what a much simpler and syntactically cleaner Dart getter would do. Consider a @ComputedProperty and a vanilla getter that produce identical results in the <x-pizza-toppings> Polymer element:
@CustomTag('x-pizza-toppings')
class XPizzaToppings extends PolymerElement {
  // ...
  List<String> model = toObservable([]);

  @ComputedProperty("model.length")
  int get numToppings => readValue(#numToppings);

  int get numToppings_old => model.length;
  // ...
}
The values of both will report the number of toppings that are included on a half or whole of pizza in the parent <x-pizza> element. To prove that both the getter and the much more verbose @ComputedValue are IDENTICAL IN EVERY WAY, I include both in the <template>:
<link rel="import" href="../../../packages/polymer/polymer.html">
<polymer-element name="x-pizza-toppings">
<template>
<p>
<!-- ... -->
      (Currently: {{ numToppings }}, {{ numToppings_old }})
</p>
</template>
<script type="application/dart" src="x_pizza_toppings.dart"></script>
</polymer-element>
And, when I update the different sides of the pizza in the UI, these bindings are updated in exact unison:



But there must be a reason for @ComputedValue. There simply must be!

Experimentation is clearly getting me nowhere, so I switch back to reading the actual code. And, thankfully, the code is beautiful. Well, documented, easy to follow for the most part. I could (and probably should) read through this all night long. But first, I must know.

And I finally have my answer from the _PropertyAccessor internal class which services both the @ComputedValue and @published annotations. In there, I find that, when these are updated, they perform the following:
  /// Updates the underlyling value and fires the expected notifications.
  void updateValue(T newValue) {
    var oldValue = _value;
    _value = _target.notifyPropertyChange(_name, oldValue, newValue);
    _target.emitPropertyChangeRecord(_name, newValue, oldValue);
  }
There is even documentation expressing what I should have expected all along—that a changed record is emitted by the Polymer element. And, sure enough, such a record is emitted. I add a listener in the parent <x-pizza> element:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizzaToppings get firstHalf => $['firstHalfToppings'];
  XPizzaToppings get secondHalf => $['secondHalfToppings'];
  XPizzaToppings get whole => $['wholeToppings'];
  // ...
  XPizza.created(): super.created() {
    firstHalf.changes.listen((changes) {
      print(changes);
    });

    /*** I previously tried this (I was so close) ***/
    // firstHalf.numToppings.changes.listen((changeRecord) {
    //   print("$changeRecord");
    // });
  }
  // ...
}
Which reports that a change has occurred:
[#<PropertyChangeRecord Symbol("numToppings") from: 0 to: 1>]
[#<PropertyChangeRecord Symbol("numToppings") from: 1 to: 2>]
[#<PropertyChangeRecord Symbol("numToppings") from: 2 to: 3>]
I have my answer. And it is one that I really should have gotten to faster had I thought about it a bit more. Ah well, in the end I had some frustration but was rewarded with some pleasant code reading—and the answer to my question.


Day #145

No comments:

Post a Comment