Bridging Angular and Polymer
In a recent project I was asked to integrate a 3rd party web components library (using Polymer) into an existing Angular host single page application (SPA). Simply embedding Web Components in Angular templates of course worked right out of the box, however achieving two-way communication between Angular and Polymer required a little more work.Now it is true that generally speaking Angular and Web Components are competitive frameworks (just as React and Angular are), yet that still doesn’t mean we can’t use them together.
In this post I’ll show you how to bridge communication between an Angular 1 host application and child web components. Specifically I will show you how to achieve:
- One-way data binding (Angular host → Web Component)
- Two-way data binding (Web Component → Angular host)
- Invoking callbacks (Web Component → Angular host)
Web Components – a Brief Overview
In a nutshell, Web Components are a set of standard features the W3C is currently working on, to allow the creation of reusable components in web applications, promoting the component model – encapsulation, interoperability and reusability. These features include:
- Custom Elements – APIs for defining proprietary/custom HTML elements with logic, attributes and properties
- Shadow DOM Support – a browser’s ability to render a subtree of DOM elements, which are otherwise hidden from the normal DOM.
- HTML Imports – methods for declaring and importing HTML modules/documents into other documents
- HTML Templates – A new element allowing documents to insert other HTML snippets
With web components, the Shadow DOM is the framework, while HTML is the template system.
Since Web Components are not just here yet, and while the W3C is slowly (since 2011) working on agreeing upon and formalizing the standards, Google has in the meantime provided Polymer – a lightweight polyfill library for creating custom web components today.
In this post I’ll thus use the terms “Web Components” and Polymer interchangeably.
A Simple Web Component
Let’s create a simple stock-item web component which we’ll use throughout this post:
The code is pretty much self explanatory. We’re declaring a simple module containing a web component, definition of its data attributes, scoped custom styling, and an HTML template conditional a conditional template block.
One Way Data Binding
Now let’s incorporate our web component in our Angular application. We’re going to use it to render a list of stock items from our Angular scope (controller):
As far as Angular is concerned there is nothing special here, and this actually works out of the box. Now, every time our stockItems model changes (in our Angular scope) it will be automatically reflected in the item-container web component.
Note, that if we were to do the opposite- include Angular templates within web components then we’d have a problem – clashing expression interpolation symbols (since both Polymer and Angular use {{ … }} for expression interpolation). In that case a possible solution would be using Angular’s $interpolateProvider to define different the start and end symbols (e.g. $[ … ]$ ).
Two Way Data Binding
For two-way binding to work (that is, allow model changes in the web component to propagate up to the Angular model) we’ll need to do two things:
First we tell the web component to reflect property changes up to its attributes, for example:
And secondly, on Angular side we need to intercept the wec component’s attribute changes and update the corresponding controller model accordingly. For that magic to happen we can use a custom directive (such as this one) to watch for attribute changes and apply them back into the original Angular scope model (note below the bind-polymer attribute):
We now have two-way binding working, Angular → Polymer → Angular.
Invoking Callbacks
Invoking custom host methods (callbacks) from the hosted element is an essential communication flow pattern. In fact, in most cases this is the preferred way of letting a child component interact upwards with its host component, as it promotes modularity, reusability and data immutability.
Web components can raise custom events by calling fire() along with named arguments. Let’s amend our web stock-item web component to include a delete button and click handler which trigger a deleteItem event:
On the Angular host side we now need to attach an event handler:
Note the bind-polymer-events attribute – this is a custom, home-made Angular directive that automatically attaches event handlers to all on-xxx events, and invokes the corresponding Angular callback scope function, passing in the respective named arguments.
Now we have most of the plumbing laid out and all we need to do is implement the custom event handler in our Angular controller:
Hope I didn’t get you out of your element :)
-Zacky