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:
my-list
is coupled withmy-fetcher
. Hence, it will be difficult to swap it.my-fetcher
has to fire thefetch
event. If it is your component, it is easy to achieve, but if you use external component which supports data binding only, it is not that easy;- the
attached
anddetached
methods are called multiple times. It means we will look formy-fetcher
component multiple times and listeners will be added/removed multiple times until Polymer.js has finished initialization.
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.