If you’ve been following our blogs you know that we’ve been using Polymer to build a lot of cool stuff in the Nuxeo Content Services Platform. In fact, we are using Polymer and Web Components more and more every day to build applications and I’m not just talking about our core development team, our solution architects are having a great time too building awesome proof of concepts - which I’d love to show and tell you all about but then I’d have to kill you.
As one of the lucky guys who first got a chance to “play” with Polymer at Nuxeo, I normally get a lot of questions from people new to the framework. So I thought it would be a good idea to start writing about some of the underlying concepts to help people understand the “Polymer way” - and of course save me some time in the future.
Data Binding
One of the concepts I see people struggling the most is data binding. This is one of those concepts we take for granted as it’s something we’ve come to expect from any modern framework. I’ve been a big fan ever since I started building Flash based RIAs (Rich Internet Applications) with Laszlo and Flex, more than 10 years ago, especially since both of them had nice declarative syntaxes for data binding. So it has become a part of my toolbox.
Polymer has built-in support for data binding. Although in the early Polymer versions (pre 1.0) more complex expressions were supported, it did feel a bit too “magical” and its expressiveness came at a huge cost in terms of performance. So with Polymer 1.0, the team introduced a very simple and clever implementation built around the Mediator pattern.
It’s so simple that whenever I see someone struggling with it I really enjoy explaining the underlying concepts and implementation and don’t let them get away until they understand it fully (sorry guys!).
Starting at the very beginning, the whole idea with data binding is to be able to “bind” an element’s property to another property or attribute of another element. In Polymer this means we are binding/mapping/connecting/synchronizing a property from our custom element, the host, to a property or attribute of one of its child elements. What’s important to take from here is that the property always “exists” in our custom element. This means that even if you’re thinking “Hey, I’m just binding this property from this child element to this other property in this other child element” the fact is that it always goes through a local property in your custom element, even if you don’t explicitly declare it. That’s why this is the Mediator pattern - our custom element is mediating these bindings.
The best way to really understand what’s going on is to take a peek under the hood. So let’s take an example - based on the awesome “Thinking in Polymer” talk by Kevin Schaff during last year’s Polymer Summit - and remove the “sugar” that Polymer adds, layer by layer.
Let’s say we’re building a “polite-element”:
<dom-module id="polite-element"><template></template><script>Polymer({ is: 'polite-element', properties: { name: { type: String } } });</script></dom-module>
Our custom element has an <input>
whose value is bound to a “name” property. Since our element is very polite, whenever this name changes we say welcome to the person.
Let’s focus for now on the first data binding expression:
<input value="{{name::input}}">
Here we are using a two-way binding, which you can tell by the use of curly brackets {{name}}
. This means that whenever “value” changes our “name” property will be updated accordingly.
The “::input” suffix specifies which event should trigger the binding, so we’re just listening for an “input” event and updating our “name” property to <input>
value. Let’s try replacing this binding with our own version of it to see if it’s that simple:
Note: You normally just need to explicitly add this suffix when using a two-way binding to native elements or to non-Polymer elements.
<dom-module id="polite-element"><template></template>
<script>Polymer({ is: 'polite-element’', properties: { name: { type: String } }, _inputChanged: function(e) { this.name = e.target.value; }, });</script></dom-module>
And, that easily, our first data binding expression is now gone!
Now let’s focus on the next one: [[name]]
In this case we’re using one-way binding since our <span>
won’t make changes to the “name” property. The idea here is to update our <span>
‘s text content with the value of our “name” property whenever it changes so let’s try adding an observer to listen for these changes and handle the text content update ourselves:
<dom-module id="polite-element"><template></template>
<script>Polymer({ is: 'polite-element', properties: { name: { type: String, observer: '_nameChanged' } }, _inputChanged: function(e) { this.name = e.target.value; }, _nameChanged: function() { this.$.name.textContent = this.name; } });</script></dom-module>
Note: the “this.$.name” is accessing the <span id="name">
element. Any element with an id is automatically stored on a `this.<span id=”name” map for convenience.
Not that hard, right? But some of you may say I’m kind of cheating here since I’m still relying on a property and an observer that’s why Polymer is still working its “magic”. So let’s get it down to a bare minimum and remove the property and observer altogether, replacing these with a simple getter and setter:
<dom-module id="polite-element"><template></template>
<script>Polymer({ is: 'polite-element', _inputChanged: function(e) { this.name = e.target.value; }, get name() { return this._name; }, set name(v) { this._name = v; this.nameChanged(); }, nameChanged: function() { this.$.name.textContent = this.name; } });</script></dom-module>
That’s it! As you can see there’s really no magic, Polymer just adds some “sugar” and makes your custom elements more declarative and easier to understand!
This is a very simple example but I hope it made it clear how much complexity is removed if you start thinking in Polymer and start leveraging data binding, observers, expressions and computed properties to bring your custom elements to a whole new level.
Finally, here are some quick closing remarks: as pretty much anything else there are pros and cons to data binding and it does tend to have a bad reputation, especially two way data binding since it can become a nightmare to debug and to understand the data flow when used extensively in bigger applications. That’s one of the reasons why new patterns like Flux are gaining popularity but as we all know there’s no silver bullet and as long as people are aware of its pros and cons I think it’s a solid choice that should be part of your toolbox when building your applications.
Have fun!