Thursday, January 16, 2014

Day 998: Binding to Model Changes in Polymer (Dart)


Tonight, I hope to better express what needs to update when the Pizza model changes in my <x-pizza> Polymer. The JavaScript version of Polymer has an observe block to do this, but I cannot find a Dart equivalent. I came up with a solution (and a pretty good one), but I do not know if it is the solution. And I must strive for the solution in Patterns in Polymer.

I currently have a simple model driven Polymer that updates the pizza state for display:



Sure, it could use some fancy graphics, but this is fine for illustration and exploration. When a human clicks the add topping buttons, the Polymer adds the selected ingredient to the model. By virtue of property change listeners, the change in the Pizza model results in an update to the current pizza state representation:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizza.created(): super.created() {
    model = new Pizza()
      ..firstHalfToppings.changes.listen(updatePizzaState)
      ..secondHalfToppings.changes.listen(updatePizzaState)
      ..wholeToppings.changes.listen(updatePizzaState);
    // ...
  }
  // ...
}
Thanks to the magic of Dart method cascades, I can establish change listeners for multiple properties on my model.

That is nice and all, but the three listeners seem a tad redundant. Really, I would be happier to listen for any changes anywhere in the model—not just changes to those individual properties. Something like that might be expressed as:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizza.created(): super.created() {
    model = new Pizza()
      ..changes.listen(updatePizzaState);
    // ...
  }
  // ...
}
I have to admit that this sort of feels like something that an Observable class ought to give me for free. That is, if any observable properties in an observable class change, then the instance changes too. But that is not the case, so I am going to need to manually do this for my Pizza class:
class Pizza extends Observable {
  ObservableList<String> firstHalfToppings = toObservable([]);
  ObservableList<String> secondHalfToppings = toObservable([]);
  ObservableList<String> wholeToppings = toObservable([]);
}
(I am not sure the word “observable” is in there enough)

Thankfully, manually notifying that the model has changed in Polymer is directly supported via the notifyChange() method. So, in the Pizza constructor, I listen to the change stream on the individual properties and, whenever such an event occurs, I notify the object itself that a change has occurred:
class Pizza extends Observable {
  ObservableList<String> firstHalfToppings = toObservable([]);
  ObservableList<String> secondHalfToppings = toObservable([]);
  ObservableList<String> wholeToppings = toObservable([]);

  Pizza() {
    firstHalfToppings.changes.listen((_)=> notifyChange(_));
    secondHalfToppings.changes.listen((_)=> notifyChange(_));
    wholeToppings.changes.listen((_)=> notifyChange(_));
  }
}
I think that is pretty close to the spirit of what I want to accomplish.

Still, I am surprised that there is nothing closer to observe blocks in the the JavaScript version of Polymer. These let me observe properties via dot notation, invoking the supplied callback when changes are observed:
  observe: {
    'model.firstHalfToppings': 'updatePizzaState',
    'model.secondHalfToppings': 'updatePizzaState',
    'model.wholeToppings': 'updatePizzaState'
  },
But if that exists, I am unable to find it.


Day #998

2 comments:

  1. Your Pizza class should be

    class Pizza extends Object with Observable{
    @observable List firstHalfToppings = toObservable([]);
    // ...
    }

    ReplyDelete
    Replies
    1. Are you sure? From the documentation (https://api.dartlang.org/docs/channels/stable/latest/observe/Observable.html), it seems like you can either mixin or extend Observable. And, from what I can tell, extending does seem to work.

      Delete