Integrate Web Components With Your Vue.js App

Joshua Bemenderfer

Even though the Web Components spec and implementations may not be considered to be mature by all, there are still quite a few interesting components and projects out there that you might find useful for your own purposes. That being said, Vue still offers a huge amount over plain’ol web components. (In fact, you can even make Web Components with Vue.) Well, why not have the best of both worlds? It’s actually really straightforward to use Web Components in Vue. Conditionals, properties, and even event bindings just keep working just like you’d expect them to.

Preparation

(This guide assumes you’ve started a quick Vue.js project with vue-cli and the webpack-simple template.)

Let’s go ahead an create a simple web component that illustrates properties and events with as little code as possible….

Behold: The ticking paragraph! Basically, it’s a wrapper for a paragraph. You set its text content through the content attribute. Oh, and it emits a tick event every half second or so for some reason. Yes, I lack creativity and / or the capability to come up with anything practical. Moving on.

ticking-paragraph.html

<template id="x-ticking-paragraph">
  <style>
    p {
      color: #42b983;
    }
  </style>
  <p id="renderTarget">
  </p>
</template>

<script>
  const currentScript = document.currentScript;

  customElements.define('x-ticking-paragraph', class extends HTMLElement {
    static get observedAttributes() { return ['contents'] }

    constructor() {
      super();
      let shadowRoot = this.attachShadow({mode: 'open'});
      const template = currentScript.ownerDocument.querySelector('#x-ticking-paragraph');
      const instance = template.content.cloneNode(true);
      shadowRoot.appendChild(instance);

      this.contents = '';

      setInterval(() => {
        this.dispatchEvent(new Event('tick'));
      }, 500);
    }

    set contents(value) {
      this._contents = value;
      this.shadowRoot.getElementById('renderTarget').innerText = this._contents;
    }

    get contents() {
      return this._contents;
    }

    attributeChangedCallback(name, oldValue, newValue) {
      this[name] = newValue;
    }
  });
</script>

I know, I know. I’m using the old HTML-imports style of component here because it feels more elegant… for a more civilized age. Also it totally looks like a Vue Single File Component doesn’t it? (Vue was inspired in part by the original Web Components spec.)

I’m not going to explain it all here, rather, if you’d like to see how to create a more proper Web Component for Modern Times™, we’ve covered that too.

Alright, so now we need to load it into the page somehow. We’ll also grab a polyfill along the way so this works in more browsers than just Chrome.

ticking-paragraph.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue + Web Components</title>
    <!-- Web Components Polyfill -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.13/webcomponents-lite.js"></script>
    <!-- Loading our component -->
    <link rel="import" href="./ticking-paragraph.html">
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>

Now that we’ve done all that, we can finally use our component in Vue as stated by the title directly following this paragraph.

Using Your Component In Vue

Really the only thing we have to do to make Vue work with this component is whitelist it in Vue.config.ignoredElements. This just tells Vue that the element / component comes from a source that Vue doesn’t know about, and keeps the compiler from complaining.

src/main.js

import Vue from 'vue';
import App from './App.vue';

Vue.config.ignoredElements = [
  'x-ticking-paragraph'
]

new Vue({
  el: '#app',
  render: h => h(App)
});

Now, use the component like any other! Reactivity stays intact!

src/App.vue

<template>
  <div id="app">
    <h1>Vue ❤ Web Components</h1>
    <x-ticking-paragraph :contents="paragraphContents" @tick="logTick"></x-ticking-paragraph>
  </div>
</template>

<script>
export default {
  data() {
    return {
      paragraphContents: `I'm data from Vue rendering in a Web Component!`
    }
  },

  methods: {
    logTick() {
      console.log(`The paragraph ticked again. >_>`)
    }
  }
}
</script>

The best part is, you don’t need to make any changes to the Web Component or to your Vue app to somehow force them to grudginly work together. It works right out of the box! In fact, you can even integrate elements that internally use other frameworks like Polymer or React.

Now, how useful this ends up being to you depends entirely on whether or not you are (or want to be) using Web Components alongside Vue. That said, if nothing else, it does indicate that if Web Components are the future, Vue is future-proof.

✖ Clear

🕵 Search Results

🔎 Searching...