Code and demo for creating Web Component using LitElement

First, let’s create our Web Component file named custom-input.js with the below code. ES6 is used for composing Web Components in LitElement.

import { LitElement, html } from "lit-element";

class CustomInput extends LitElement {
  render() {
    return html`
      <style>
        :host {
          display: block;
        }
        :host([hidden]) {
          display: none;
          box-sizing: border-box;
        }
        input {
          background: #ffffff;
          border-radius: 20px;
          border-style: none;
          font-size: 15px;
          font-family: Roboto;
          font-style: normal;
          font-weight: bold;
          line-height: normal;
          width: ${this.width};
          border: 1px solid lightgrey;
          padding: 5px;
          padding-left: 10px;
          box-sizing: inherit;
        }
        input:focus {
          outline: none;
          box-shadow: 0 0 0 1px blue;
        }
      </style>
      <input
        id="${this.inputId}"
        type="text"
        .value="${this.cityProp}"
        @input="${this.handleInput}"
      />
    `;
  }

  static get properties() {
    return {
      cityProp: {
        name: "cityProp",
        type: "String",
        value: " ",
        reflect: true,
        attribute: true,
        observer: false
      },
      inputId: {
        name: "inputId",
        type: "String",
        value: " ",
        reflectToAttribute: false,
        observer: false
      },
      width: {
        name: "width",
        type: "String",
        value: " ",
        reflectToAttribute: false,
        observer: false
      }
    };
  }

  constructor() {
    super();
    this.cityProp = "";
    this.inputId = "";
    this.width = "100%";
  }

  handleInput(e) {
    this.cityProp = e.target.value;
  }

}

customElements.define("custom-input", CustomInput);
export { CustomInput };

Let’s take a closer look at the code above. In order to construct a Web Component in LitElement, in your custom Web Component class you will need at least three parts:

  1. A getter for the property properties that will define an object with which properties can be passed into your custom element. For more information on the configuration of the properties object check out the LitElement documentation on that subject here. You can also define these properties using a TypeScript decorator instead of using a getter.
  2. A constructor where you can define the default values of the properties of your element.
  3. A render method where you will define the styles and html elements to be rendered in the Shadow DOM. LitElement uses lit-html which lets you write HTML templates in JavaScript using template literals with embedded JavaScript expressions.

In the render method of the custom class, you will see that the input element looks a little different than a regular HTML element. This is because lit-html allows for different types of bindings aside from attribute bindings.

Next, generate an index.html file. In order to use your custom Web Component inside your index.html file, you must import the Web Component JavaScript module we just made.

<script type="module" src="./src/custom-input.js"></script>

Also, add the below script to the html file so that it will provide the correct polyfills for older browsers that partially or does not support Web Components.

<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>

To see the list of browsers this polyfills works for as well as other information about this library, check out the GitHub repository here.

To display the Web Component in the DOM, add the custom element tag into the body of the HTML document.

Development pipeline for a Web Component library

In order to build a Web Component library, it would be the best to have all the components in a mono-repository for maintainability and development. The aim of having a Web Component library is to be able to have a set of the latest components that are available easily to your users that can be imported seamlessly into the JavaScript framework they are using.

Lerna is a good tool to have in your pipeline to keep track of and publish versions of your Web Components to a package manager like NPM. Lerna can also take care of your components interdependencies between each other and create symlinks to enable easier local development. Lerna can update your package.json with the latest version of the new components so they point to the correct node modules.

Storybook integration can also be helpful as documentation for your Web Components library. It allows you to see all your components and their different variations visually in one place. Storybook for Polymer is an experimental Polymer plugin for storybook that works with LitElement.

Testing is another major consideration when developing a component library. Polymer provides a tool to test Web Components called web-component-tester that provides a browser-based testing environment, configured out of the box.

An emerging pipeline tool for Web Components called Web Component Factory allows you to build Web Components in an environment already setup with Lerna, Storybook, web-component-tester and other tools. I utilized Web Component Factory to build my Web Components library called weather app components. There were a few tweaks that I had to do to get all the components working properly with the tool. You can try forking my repository and adding a new element into the library yourself and try publishing it to npm.

In the weather app components library I have made several Web Components that will help me build a Weather App. The custom components are all published in npm here.

Using Web Components in Javascript frameworks/libraries

Web Components are generally well supported by major JavaScript frameworks. There are some challenges with React which can be resolved with well defined workarounds. The Custom Elements Everywhere site gives you a good overview of framework support and any issues that you might run into. As Web Components become increasingly popular, the frameworks are resolving those issues and improving support.

Next, I'll show you how to import the weather app custom Web Components into a ReactJS and Angular application.

Using Web Components in ReactJS

Below is the finished code for utilizing the <custom-input>, <custom-input-button>, and <custom-info-card> Web Components in a ReactJS v16.7 application that takes a city input and displays the weather data in a custom info card when the user clicks on the custom button.

To use the Web Components into an React App, npm install the components you want to use:

npm install @amyscript/custom-info-card

npm install @amyscript/custom-input

npm install @amyscript/custom-input-button

Then import the Web Component that you want to use in the .js file of your React Component that will be displaying the Web Component.

In this case, we will import the Web Components into the App.js file:

import '@amyscript/custom-info-card/custom-info-card.js';

import '@amyscript/custom-input/custom-input.js';

import '@amyscript/custom-input/custom-input-button.js';

And then in your render method, you would insert the Web Component using the custom tag. Everything seems to be working fine except one issue: my callback that I’ve passed to the buttonFunction prop isn’t executing for <custom-input-button>.

As you can see in the Custom Elements Everywhere website, there are two main issues with integrating Web Components with React and one of them is handling non-primitive data:

'React passes all data to Custom Elements in the form of HTML attributes. For primitive data this is fine, but the system breaks down when passing rich data, like objects or arrays. In these instances you end up with stringified values like some-attr="[object Object]"which 'can't actually be used.’

The workaround for this is to use a third party library like skatejs-react-integration. Once you have wrapped your Web Component with this library, you can pass in props to your component.

Using Web Components in Angular

To bring the Web Components into an Angular App, npm install the components you want to use:

npm install @amyscript/custom-info-card

npm install @amyscript/custom-input

npm install @amyscript/custom-input-button

Then in your app.module.ts file, you will have to import CUSTOM_ELEMENTS_SCHEMA from @angular/core and define it in the schemas property. You can import all your Web Components into the app.module.ts file and you will be able to use any of the Web Components in any template files in your project.

import { BrowserModule } from "@angular/platform-browser";
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { AppComponent } from "./app.component";

import "@amyscript/custom-info-card";
import "@amyscript/custom-input";
import "@amyscript/custom-input-button";

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})

export class AppModule {}

Next, in the tsconfig.json file, set the property “allowJS” to true so that Angular will allow import of .js files. Also, ensure that the “target” property is set to “es6”. If you need the code to be transpiled to “es5”, ensure that you include the custom-elements-es5-adapter because ES5-style classes cannot properly extend ES6 classes, like HTMLElement. Without the custom-elements-es5-adapter, any browsers that natively support Custom Elements will throw an error.

Then, add your Web Components in a template file using its custom tag. Since Angular’s default binding syntax always sets properties on an element, we won’t run into the same issue as our integration with ReactJS.

You can check out the final working app on CodeSandBox:

Web Components and the future

No doubt Web Components will become more popular as frameworks, libraries and browsers continue to make their integration friendlier. As more companies choose to develop their design systems and component libraries to be JavaScript framework agnostic, Web Components prove to be a viable solution in minimizing development time and maximizing ease of maintenance.