Data sharing between web-components on Polymer.js

Posted: May 1, 2016

Data binding is a default approach to setup data sharing between web-components built on Polymer.js.

<template is="dom-bind">
  <my-fetcher items="{{items}}"></my-fetcher>
  <my-list items="[[items]]"></my-list>
</template>

Here my-fetcher gets data from some storage and my-list consumes that data. At some point it looks like these 2 components are independent, they only share data. If you want to replace my-fetcher with another component, you can easily do that. There is only one requirement, that another component must provider items.

Actually, there are some limitations which you will discover once you get deeper. Your components won't look that simple as it is in the example above.

Limitation #1

In the example above my-list listens to changes happened to items. Once items are there, they get shown. But, there are cases when a component mustn't know about changes happened to items.

<template is="dom-bind">
  <my-list items="{{items}}"></my-list>
  <my-saver items="{{items}}"></my-saver>
  <my-success-msg items="[[items]]"></my-success-msg>
</template>

Here my-list shows a list of items and provides a way to change that list (add/remove items). my-saver saves items once they are changed. my-success-msg shows a success message once items are changed (let's say we think positively and we show a message about an added/removed item before changes get saved. Now this approach is very common for web projects). But, if the storing via my-saver fails, my-saver has to rollback changes which happened to items. If we keep the code as it is now, my-success-msg will be notified about changes happening during rollback. As a result it will show a message when it is inappropriate. Hence, the solution has to be more complex:

<template is="dom-bind">
  <my-list items="{{items}}"></my-list>
  <my-saver items="{{items}}" rolling-back="{{rollingBack}}"></my-saver>
  <my-success-msg items="[[items]]" disabled="[[rollingBack]]"></my-success-msg>
</template>

my-saver should expose a property telling when rollback happens and my-success-msg has to be told to not show any message during rollback. It isn't so bad, but in this case my-success-msg is driven by another component. Also, let's imagine there is another component which has to do something with items and my-success-msg must not be notified about changes.

Limitation #2

Polymer.js does not provide any API to setup data binding for dynamically created components.

var myList = document.createElement('my-list');
myList.items = document.querySelector('my-fetcher').items;

my-list won't be able to catch changes happened to items. I work on a project where we don't want any heavy JavaScript framework. We use some jQuery plugins which work fine for us and some of them change DOM. Hence, they may change DOM where web-components are. In this case data binding doesn't work at all. Data binding works only for components wrapped with auto-binding template:

<template is="dom-bind">
  <!-- html here -->
</template>

Limitation #3

Let's say you have components placed in 2 different parts of a page.

<header>
  <!-- HTML code -->
  <my-items-count></my-items-count>
  <!-- HTML code -->
</header>
<main>
  <!-- HTML code -->
  <my-list></my-list>
  <!-- HTML code -->
</main>

If you want to share data between my-items-count and my-list, the whole page should be wrapped with the auto-binding template.

<template is="dom-bind">
  <header>
    <!-- HTML code -->
    <my-items-count items="[[items]]"></my-items-count>
    <!-- HTML code -->
  </header>
  <main>
    <!-- HTML code -->
    <my-list items="{{items}}"></my-list>
    <!-- HTML code -->
  </main>
</template>

It might be fine if Polymer.js is used on all pages. You can wrap your layout with the auto-binding template and it will work fine. But, there are projects which would like to benefit from components on a few pages only. Hence, you will have to provide a way to turn on/off the auto-binding template in your layout, otherwise, your page won't be rendered.

Unfortunately, multiple auto-binding template doesn't work.

<header>
  <!-- HTML code -->
  <template is="dom-bind">
    <my-items-count items="[[items]]"></my-items-count>
  </template>
  <!-- HTML code -->
</header>

<main>
  <!-- HTML code -->
  <template is="dom-bind">
    <my-list items="{{items}}"></my-list>
  </template>
  <!-- HTML code -->
</main>

Here data isn't shared.

Are there other ways besides data binding?

When data binding doesn't work, you can use usual listeners.

var myFetcher = document.querySelector('my-fetcher');

myFetcher.addEventListener('fetch', function() {
  document.querySelector('my-list').items = myFetcher.items;
});

But, it also comes with limitations you have to deal with.

<my-fetcher></my-fetcher>
<my-list></my-list>

my-list wants to know when my-fetcher fetches data.

Polymer({
  is: 'my-list',

  // properties and methods

  attached: function() {
    this.attachedShowItemsFn = this.showItems.bind(this);

    this.fetcher = document.querySelector('my-fetcher');
    this.fetcher.addEventListener('fetch', this.attachedShowItemsFn);
  },

  detached: function() {
    this.fetcher.removeEventListener('fetch', this.attachedShowItemsFn);

    // the detached method is called when the component is removed.
    // Hence, we need to remove external references.
    this.fetcher = null;
  },

  showItems: function() {
    this.items = this.fetcher.items;
  }
});

There are a few bad things:

As you can see this solution comes with additional code and costs.

Conclusion

Polymer.js looks a very promising library for building web components. The good thing about web components is that most likely their features will be built into browsers. I do like benefiting from built-in features. But, in my opinion the data binding feature provided by Polymer.js isn't enough for data sharing. You will have to use data binding and usual event listeners. Both ways aren't perfect.

Maybe if you built SPA, all things I mentioned above doesn't bother you. But, I've been considering this library for our Rails application where HTML is supplied by the backend framework and Polymer.js might be used on a few pages only. Also, Polymer.js won't have monopoly, DOM may be changed by jQuery plugins or our custom code.