Wednesday, February 5, 2014

Angular and Polymer Data Binding, Together!





Angular and Polymer, sitting in a DOM tree,
B-i-n-d-i-n-g.
First comes components,
Then comes elements,
Then comes the interop with the node dot bind.

Angular, a super heroic MVC framework, and Polymer, polyfills and enhancements for custom elements built on top of Web Components, can live harmoniously in the same app. This post shows you how to connect Angular-controlled components to Polymer-controlled elements via data binding. And we do it all in Dart.

Angular and Polymer


I get asked "Should I use Angular or Polymer?" a lot. My answer is, "Yes". That is, both libraries have distinct strengths, and you can use both in the same app.

Polymer excels at creating encapsulated custom elements. You can use those custom elements in any web app or web page, regardless if that app is built with Angular, Ember, etc. Angular excels at application engineering, with dependency injection, end-to-end testability, routing, and services.




Here are some features that Angular offers:
  • directives - kind of like mixins or traits for DOM nodes
  • dependency injection
  • testing support
  • routing
  • services
  • server communication
Here are some features that Polymer offers:
  • <polymer-element> - convenient way to declare custom elements
  • widgets
  • polyfills for new web platform features
  • encapsulation for the DOM
  • built on emerging web standards
There are some similar features between the two libraries. Both libraries implement a data binding solution (which we cover below). Angular is beginning to build on top of emerging web standards (like Shadow DOM). I expect that, over time, more and more of Angular will take advantage of the Web Components family of specifications.

Interop

We can interop Angular and Polymer via the DOM, as well as via data binding.

DOM interop


We've always been able to use the DOM for basic interop between custom elements and Angular. For example, setting an attribute on a custom element is a type of API interop. Simply creating the custom element and inserting it into the document is another type of interop. Polymer and Angular already interop at the DOM layer.

For example, here's an example of an Angular managed page that includes a Polymer element.



In the above code, Angular manages the binding of the cool variable in the HTML. When cool changes, everywhere you see {{cool}} also changes. Yes, even when bound into an attribute of a custom element!

The <my-element> is just a custom Polymer element encapsulates an input field and displays a value. Here's the code:


<polymer-element name="my-element">
  <template>
    <p>
      <input type="text" value="{{message}}"> Polymer binding: {{message}}
    </p>
  </template>
  <script type="application/dart" src="my_element.dart"></script>
</polymer-element>

The Angular code can set attributes on <my-element>, listen for DOM events from <my-element>, and of course even insert children elements inside of <my-element>. So in a sense, Angular can interop with Polymer elements just like any other DOM element.

However, there is an issue. Can you spot it in the animated GIF below?


Any changes to cool initiated from inside the Polymer element are not propagated back out into the outside DOM or Angular. We need real bi-directional data binding: Angular down into Polymer, and Polymer back out to Angular.

Data binding interop


Polymer manages data binding through a feature called Node.bind(). Node.bind() is a "new method added to all DOM nodes which instructs them to bind the named property to the data provided". Angular does not use Node.bind() out of the box, so Angular is unable to listen for changes that are initiated from Polymer.

Until now.

Justin Fagnani, an engineer on the Dart team, released angular_node_bind, a Dart package that bridges the Angular and Polymer worlds. It is an Angular module that can listen for Node.bind() changes and propagate the changes into Angular.

Justin explains why this package can help:

  • Node.bind() takes care of setting up the binding, including two-way bindings, eliminating the need for directives for every property for two-way bindings.
  • Custom elements that expose properties will be properly bound, again including two-way bindings. You can use the growing collection of custom element libraries in your Angular app.

To use angular_node_bind, add the following to your app's pubspec.yaml file:


name: angularpolymer
description: A demo of Angular and Polymer data binding.
dependencies:
  angular: any
  angular_node_bind: any
  browser: any
  polymer: any
transformers:
- polymer:
    entry_points: web/angularpolymer.html

Register the NodeBindModule with Angular:


import 'package:polymer/polymer.dart' show initPolymer;
import 'package:angular/angular.dart' show ngBootstrap;
import 'package:angular_node_bind/angular_node_bind.dart' show NodeBindModule;

void main() {
  initPolymer().run(() {
    ngBootstrap(module: new NodeBindModule());
  });
}

Finally, change the syntax used for binding into Polymer elements, from {{...}} to [[...]]:


<my-element message="[[cool]]"></my-element>

Notice the binding expression is now [[cool]] instead of {{cool}}. The angular_node_bind module looks for [[ ... ]] and wires up Node.bind().

Here is a little movie of how the app works with true bi-directional data binding between Polymer and Angular:



Notice how changes from Angular are reflected in the Polymer element, and how changes from within the Polymer element are reflected in the Angular code. Awesome!

Summary

You can use Polymer custom elements inside of an Angular app in at least two ways. Polymer elements are just like regular DOM elements, so they have attributes, emit events, and can contain children elements. Use Dart's angular_node_bind package to connect Polymer's data binding to Angular's data binding for true bi-directional data sharing.

Lovely, Angular and Polymer holding hands.

Get the source code

You can check out the source code mentioned in this post.
Post a Comment

Disclaimer

I'm probably required to say that the views expressed in this blog are my own, and do not necessarily reflect those of my employer. Also, except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the BSD License.