Web Components with Polymer framework

by Javier Puerto
Tags: Open Source , Web , Web Components , Polymer

In our previous posts we created Web Components with X-Tag and Vue.js. It is time to continue this series with another episode. This time we will see Polymer in action.

Tools to build something bigger. Building with Web Components. (Foto: pexels.com)

What is Polymer?

Polymer is a library to help building reusable Web Components and provides a set of features for creating custom elements. These features are designed to make it easier and faster to create custom elements that work like standard DOM elements. Polymer 3.0 has moved from HTML Imports to ES6 Modules, and from Bower to npm. Personally I think that this is a step forward to bring the latest development facilities to Web Components.

Creating components with Polymer

We are going to use the CLI tool for easier development. First we have to install polymer-cli.

npm install -g polymer-cli

Then we create a new polymer project.

mkdir be-media
cd be-media
polymer init
> polymer-3-element

From this point we can run the component demo.

polymer serve

Creating be-media component

We are going to create a component that renders as an image or a video based on the src attribute value. The component is similar to the ones previously created with X-Tag and Vue.js.

Polymer allows us to define the component's template in the template property. This HTML will be attached to a shadow DOM created for the component. The shadow DOM feature allows that every component has its own DOM tree that can be styled or manipulated by the component. In this case we are using the dom-if component to provide the conditional rendering.

Then we have to define a couple of properties as we did before for our previous components:

  • src: that will contain the URL to the media resource.
  • class: that will allow us to style our component as we want.

The final component code is as follows:

import {html, PolymerElement} from '@polymer/polymer/polymer-element.js';
import '@polymer/polymer/lib/elements/dom-if.js';

/**
 * `be-media`
 * Component to render media elements based on "src" extension.
 *
 * @customElement
 * @polymer
 * @demo demo/index.html
 */
export class BeMedia extends PolymerElement {

  constructor() {
    super();
  }

  static get template() {
    return html`
        <template is="dom-if" if="[[_renderVideo(src)]]">
            <video class$="[[className]]" autoplay> <source src="[[src]]" type="video/mp4"> </video>
        </template>
        <template is="dom-if" if="[[!_renderVideo(src)]]">
            <img class$="[[className]]" src="[[src]]"/>
        </template>
    `;
  }

  _renderVideo(src) {
      return src.endsWith('.mp4');
  }

  static get properties() {
    return {
      src: {
        type: String,
        value: '',
      },
      'class-name': {
        type: String,
        value: '',
      },
    };
  }
}

window.customElements.define('be-media', BeMedia);

Polymer supports one way bindings [[]] and two ways bindings {{}}. The library will automatically generate getters and setters and depending on the type of binding it will attach an observer to update the model or view respectively, when data is changed. We can also configure the binding type directly in the properties definition by adding the properties notifyand readOnly. See data binding from Polymer's documentation for details.

To control the view, we rely on a private method _renderVideo(src) that will check if the src URL ends with .mp4 extension and returns true or false. But why do we need to pass the src attribute to this method even the information is already in the class? As we have explained before, Polymer will attach observers to bound attributes, so if we omit the src parameter from the template conditional, Polymer will not trigger an update of the templates when the src attribute changes as all the information it has, is a method. Adding src will tell Polymer that this value affects also to the rendering.

Testing be-media component

Polymer includes a configured test suite with Selenium, Sinon and Chai. It is very easy to work with, we just have to run the component with

polymer serve

And visit the page:

http://localhost:8081/components/[component-name]/test /[component-name]_test.html

For our be-media component, the URL will be:

http://localhost:8081/components/be-media/test/be-media_test.html

After we update our test cases, we can just run polymer test and Selenium will take care of opening compatible browsers and running tests.

Creating be-dog and be-cat components

Both components are practically identical so we are going to handle them as a single component. We start creating a new project with polymer.

be-media> cd ..
> mkdir be-dog
be-dog> cd be-dog
be-dog> polymer init
> polymer-3-element

For the dependencies we are going to use the same as be-media so after the project has been initialized, we need to edit package.json and include be-media and axios.

"dependencies": {
    "axios": "^0.18.0",
    "be-media": "../be-media"
  },
  "devDependencies": {}

Please note that the project directories must be siblings to work. Then we should update access to the Polymer resources to point to be-media/node_modules/ instead of the default paths.

import {BeMedia} from 'be-media/be-media.js'
import {afterNextRender} from 'be-media/node_modules/@polymer/polymer/lib/utils/render-status.js';

/**
 * `be-dog`
 * Component to render random dogs&#39; media elements based be-media.
 *
 * @customElement
 * @polymer
 * @demo demo/index.html
 */
class BeDog extends BeMedia {

    constructor() {
        super();
        this.refresh();
        afterNextRender(this, function() {
            this.addEventListener('click', this.refresh);
        });
    }

    refresh() {
        axios.get('https://random.dog/woof.json')
            .then(response => this.src = response.data.url)
            .catch(error => console.error(error));
    }
}

window.customElements.define('be-dog', BeDog);

This time we just include a public method refresh() and override the default constructor of be-media by calling the refresh button and binding the click event. To register the click event, we use the method afterNextRender(). This method will run after the component has been initialized so it is perfect to attach the behaviour and also speeds up the rendering.

That's all folks! For be-cat component, the procedure is almost identical. Just replace the "random.dog" service with the "random.cat" service.

    refresh() {
        axios.get('https://aws.random.cat/meow')
            .then(response => this.src = response.data.file)
            .catch(error => console.error(error));
    }

Thanks for reading

The experience with Prototype was a success, the library has a good documentation and it is easy to work with. You can check the code for this tutorial at GitHub

As always, I hope you enjoyed and learnt something from this tutorial. If not you may at least had fun with all the dogs and cats. See you in the next blog post.