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:
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:
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.
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:
To use angular_node_bind, add the following to your app's pubspec.yaml file:
Register the NodeBindModule with Angular:
Finally, change the syntax used for binding into Polymer elements, from {{...}} to [[...]]:
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!
Lovely, Angular and Polymer holding hands.
- <polymer-element> - convenient way to declare custom elements
- widgets
- polyfills for new web platform features
- encapsulation for the DOM
- built on emerging web standards
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.