Saturday, February 1, 2014

Pure Polymer Changes from the Outside (Dart) World


The question that I need to answer today is, how do I observe Polymer attribute changes in Dart?

For the past week or so I needed to learn how to do just that in the JavaScript version of the project. Since the ultimate goal was to use it inside an Angular application, I skipped over this investigation in Dart at first—Dart sports that angular_node_bind to make this nearly trivial. Still, I would like to include a chapter on this in both the Dart and JavaScript version of Patterns in Polymer, so…

I have a very simple <hello-you> Polymer in Dart form:
@CustomTag('hello-you')
class HelloYou extends PolymerElement {
  @published String your_name;
  @published String color;
  // ...
}
I would like for external code to be able to listen to changes for either of those attributes from outside of the Polymer. Inside the Polymer is easy—I can just listen to the changes property that Polymer uses to expose a stream of… changes. As I found a couple of days ago, this can be problematic in JavaScript between the observe block and changed watchers. But, in Dart, this is just a stream. So maybe it is a simple as adding another listener?

Since this is Dart and this code will be external to Polymer, I cannot use the simple Polymer initialization script:

    <!-- THIS WON'T WORK -->
    <link rel="import" href="packages/changes_from_outside/elements/x-pizza.html">
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
Instead I load the main() entry point in the the form of main.dart:
    <link rel="import" href="packages/changes_from_outside/elements/hello-you.html">
    <script type="application/dart" src="main.dart"></script>
And inside there, I manually initialized Polymer:
import 'package:polymer/polymer.dart';
main() {
  initPolymer().run((){
    // Actual code goes here...
  });
}
That run() is interesting. It looks like a Future, but is, in fact, a Zone in which logically connected code can be run. Really, I think the overall effect is the same in this case—there is a bunch of Polymer initialization performed by initPolymer() and, when complete, the zone is exposed for further action. In other words, all of my Polymer elements should be proper elements within that Zone.

And that seems to be the case. Inside the zone, I can run a simple query for the <hello-you> element and start listening for changes right away:
import 'dart:html';
import 'package:polymer/polymer.dart';
main() {
  initPolymer().run((){
    query('hello-you').
      changes.listen((change_list) {
        change_list.forEach((change) {
          print(
            '[changes] '
            '${change.name} changed '
            'from: ${change.oldValue} '
            'to: ${change.newValue}.'
          );
        });
      });
    });
}
In the JavaScript equivalent, that broke because the changes property (or the equivalent in JS) was not yet present. Instead, I had to introduce polling for Polymer properties before I could get to work listening for changes. The way the Dart equivalent of Polymer is written, I do not have to worry about Angular code being executed before Polymer or any other libraries getting in the way. I suppose that is the point of zones.

Anyway, that code just works™. When I make changes inside my <hello-you> Polymer, they are logged in the console:



Just for completeness sake, I investigate the manner in which angular_node_bind goes about doing this. It has a bit more generalized approach that relies on the Observable package along with some template binding:
import 'dart:html';

import 'package:observe/observe.dart';
import 'package:template_binding/template_binding.dart';

import 'package:polymer/polymer.dart';
main() {
  initPolymer().run((){
    var el = query('hello-you');

    var box = new ObservableBox();
    var binding = nodeBind(el).bind('color', box, 'value');
    box.changes.listen((changes) {
      print('[box] ${changes}');
    });
    // Regular, changes solution here...
  });
}
That works and does not interfere with the solution based on the Polymer changes property:



But I find that a bit less intuitive that the pure Polymer solution. Still, I think I have what I need for the latest chapter in the book and it seems like it won't interfere with, or be interfered by, angular_node_bind, so… win! And by win, I mean yay zones and streams!


Day #1,014

1 comment: