Web Component is an official custom component implementation that allows developers to implement custom page components without relying on any third-party frameworks (e.g., Vue, React). Achieve component reuse effect.

How to implement custom components using WebComponent

Next, I will implement a simple custom component step by step that contains a button and UL list that will be dynamically added by clicking the button.

So let’s look at the end result

Define and use the Web Component trilogy

  1. Inherit HTMLElement to implement a custom class

    class MyButton extends HTMLElement {
        constructor() {
            super();
        }
    }
    Copy the code
  2. Defining component templates

    <template>
    </template>
    Copy the code
  3. Register custom elements

    window.customElements.define(name, ComponentClass)
    Copy the code

To complete a simple custom component

  1. Define custom components

    class MyButton extends HTMLElement {
        constructor () {
            super(a);const template = document.getElementById('mybutton');
            const content = template.content.cloneNode(true);
            this.appendChild(content); }}Copy the code
  2. Defining component templates

    <template id="mybutton">
        <button>Add</button>
    </template>
    Copy the code
  3. Certified components

    window.customElements.define('my-button', MyButton);
    Copy the code
  4. Using the component

    <body>
        <my-button></my-button>
    </body>
    Copy the code

With these four steps, you can define a custom component and successfully render it to the page.

The end result is as follows

Sum up the knowledge of this section

  • Inherit the HTMLElement class using the ES6 class, and call super() in the subclass’s constructor, where you get the template content and add it to the current instance
  • Define the component template, using the
  • Through the global APIwindow.customElements.define()Register a custom component, otherwise the component will not be used
  • Use custom components just like normal tag elements

One thing need to be aware of, const content = template. The content. cloneNode (true); This line of code makes a deep copy of a template, because the template can be used in multiple places, and if you use the template itself directly, it will affect each other.

Here is the complete code

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Component</title>
</head>
<body>
  <my-button></my-button>
  <template id="mybutton">
    <button>Add</button>
  </template>
</body>
<script>

  class MyButton extends HTMLElement {
    constructor () {
      super(a)const template = document.getElementById('mybutton');
      const content = template.content.cloneNode(true);
      this.appendChild(content); }}window.customElements.define('my-button', MyButton)
</script>
</html>
Copy the code

Next we will refine and enrich the custom component step by step.

Add styles to components

We only defined a template skeleton for the component above. We did not use CSS to decorate our component. We can add styles to the component in the template.

<style>
  button {
    width: 60px;
    height: 30px;
    cursor: pointer;
    color: blue;
    border: 0;
    border-radius: 5px;
    background-color: #F0F0F0;
  }
</style>
<template>
	<button>
        Add
    </button>
</template>
Copy the code

Add a custom button after styling

How do I receive external parameters?

A component with only the above capabilities will have poor reusability because it cannot change dynamically based on parameters; Webcomponents can receive external parameters that developers can use to manipulate and render components differently.

  1. Add attributes to the custom component that need to be passed
<body>
    <my-button
    	text="Hello"           
    ></my-button>
</body>
Copy the code
  1. Get property values from custom component classes and use them as needed
class MyButton extends HTMLElement {
    constructor () {
        super(a)const template = document.getElementById('mybutton');
        const content = template.content.cloneNode(true);
+       const text = this.getAttribute('text');
+       content.querySelector('#btn').innerText = text;
        this.appendChild(content); }}Copy the code

Attributes added to the component can be obtained through this.getAttribute(name). We can add as many attributes to the component as we want.

The final render will look like this

How do I send parameters to the outside world?

If a component needs to interact with the outside world, it needs to be able to send data to the outside world.

There are two ways to do this: 1) add custom methods to the custom component, listen for DOM element events within the component, and call the custom methods of the component where needed; 2) Use the element’s custom events

1) Add custom methods

class MyButton extends HTMLElement {
    constructor () {
        super(a)const template = document.getElementById('mybutton');
        const content = template.content.cloneNode(true);
        const button = content.querySelector('#btn');
        const text = this.getAttribute('text');
        button.innerText = text;
+       button.addEventListener('click'.(evt) = >{+this.onClick("Hello from within the Custom Element"); +})this.appendChild(content); }}Copy the code
document.querySelector('my-button').onClick = value= > {
    console.log(value);
}
Copy the code

2) Use elements to customize events

  class MyButton extends HTMLElement {
    constructor () {
      super(a)const template = document.getElementById('mybutton');
      const content = template.content.cloneNode(true);
      const button = content.querySelector('#btn');
      const text = this.getAttribute('text');
      button.innerText = text;
      button.addEventListener('click'.(evt) = >{+this.dispatchEvent(
+        new CustomEvent('onClick', {
+           detail: 'Hello fom within the Custom Element'+}})))this.appendChild(content); }} +document.querySelector('my-button').addEventListener('onClick'.(value) = > 			
+	console.log(value));
Copy the code

Native slots can be defined just like Vue slot slots

Now consider the following: can slots be used in webComponent like slots in Vue? It is also possible. In fact, Vue references the webComponent specification for slot implementation. Let’s see how slot can be used in a Web Component.

The first step is to define a slot label slot placeholder in the template, and the slot can add default display content

<template id="mybutton">
    <style>
        button {
            color:blue;
            border: 0;
            border-radius: 5px;
            background-color: brown;
        }
    </style>
    <button id="btn">My Button</button>
+   <p><slot name="my-text">My Default Text</slot></p>
</template>
Copy the code

Then, when using the component, add a slot property to the content, specifying the slot to be filled in

<my-button>
+  <p>Another Text from outside</p>
</my-button>
Copy the code

The final rendering looks like this

Listen for property changes

In “How do I receive external parameters?” In the first section, we know how to pass data to components. If the data passed in changes, we expect the component content to change as well, as with Vue data binding; Similar functionality can be implemented in the Web Component.

Let’s start with three new functions

  1. static get observedAttributes()
  2. attributeChangedCallback(name, oldVal, newVal)
  3. render()

The first function is the static GET function, which defines which properties to listen for;

The second function is a callback for property changes. That is, this function is called whenever the value of a monitored property changes.

The third function is the render function, which is called to re-render the component after the property is updated.

Here’s how to listen for properties and update components (all of the following functions are added to the custom component class, which is the MyButton class for this example)

  1. Define the properties to listen on
static get observedAttributes() {
    return ['text'];
}
Copy the code
  1. Define the callback function
attributeChangedCallback(name, oldVal, newVal) {
    this[name] = newVal;
    this.render();
}
Copy the code
  1. To render
render() {
    this.$button.innerText = this.text;
}
Copy the code

This.$button needs to be defined in the constructor

The complete class definition is as follows

class MyButton extends HTMLElement {
    constructor () {
        super(a)const template = document.getElementById('mybutton');
        const content = template.content.cloneNode(true);
        const button = content.querySelector('#btn');
        const text = this.getAttribute('text');
+       this.$button = button;
        button.innerText = text;
        button.addEventListener('click'.(evt) = > {
            this.dispatchEvent(
                new CustomEvent('onClick', {
                    detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content);
    }
+   static get observedAttributes() {+return ['text']; +},attributeChangedCallback(name, oldVal, newVal){+this[name] = newVal;
+       this.render(); +},render(){+this.$button.innerText = this.text; +}}Copy the code

Property values on custom components can be dynamically changed through code, as simulated below in Chrome DevTool

Define the set and get functions for attributes

In the above example, there are two inconvenient points: 1) Each time an attribute is updated, it needs to be manually assigned in the attributeChangedCallback function; 2) Attributes can only be added to DOM elements; We want to be able to change property values directly in JavaScript code as if we were assigning values to objects.

Add a GET function to each attribute that needs to be listened on

get text () {
    return this.getAttribute('text');
}
Copy the code

Redefine the set function of the property

set text (value) {
    this.setAttribute('text', value);
}
Copy the code

We can now define attributes and assign values to component objects directly, rather than defining them on component DOM elements. The following

const mybutton = document.querySelector('my-button');
mybutton.text = 'Hi! ';
Copy the code

The life cycle

In addition to the above, we need to understand the Life cycle of a Web Component, which allows us to do what we need to do at different times in our custom component.

  1. connectedCallback

This callback is called when the Web Component is added to the DOM and is executed only once. You can do some initialization in this callback, such as parameterizing the component’s style.

  1. disconnectedCallback

Executed when the Web Component is removed from the document DOM.

  1. adoptedCallback

Executed when the Web Component is moved to a new document.

  1. attributeChangedCallback

Executed when the monitored property changes.

Below is the complete code

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <my-button>
    <p slot="my-text">Another text from outside</p>
  </my-button>
  <template id="mybutton">
    <style>
      button {
        width: 60px;
        height: 30px;
        cursor: pointer;
        color: blue;
        border: 0;
        border-radius: 5px;
        background-color: #F0F0F0;
      }
    </style>
    <div>
      <button id="btn">Add</button>
      <p><slot name="my-text">My Default Text</slot></p>
    </div>
  </template>
</body>
<script>

  class MyButton extends HTMLElement {
    constructor () {
      super(a)const template = document.getElementById('mybutton');
      const content = template.content.cloneNode(true);
      const button = content.querySelector('#btn');
      this.$button = button;
      button.addEventListener('click'.(evt) = > {
        this.dispatchEvent(
          new CustomEvent('onClick', {
            detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content);
    }
    get text () {
      return this.getAttribute('text');
    }
    set text (value) {
      this.setAttribute('text', value);
    }
    static get observedAttributes() {
      return ['text'];
    }
    attributeChangedCallback(name, oldVal, newVal) {
      this.render();
    }
    render() {
      this.$button.innerText = this.text; }}const mybutton = document.querySelector('my-button');
  mybutton.addEventListener('onClick'.(value) = > {
    console.log(value)
    mybutton.text = value
  });
  window.customElements.define('my-button', MyButton)
</script>
</html>
Copy the code

Stop there?

We’ve covered the basics of Web Component; However, we define the components directly in THE HTML document, so in the actual project can not achieve reuse effect, because we can not directly put the code on someone else’s page, but through the way of reference to call, so we want to implement modular components.

A Web Component can be defined in a JS file and referenced by others via the script tag. Let’s change the previous component.

Define a component template using a template string, write a custom component class, register, and create a new myButton.js file

The complete code is shown below

const template = document.createElement('template');
template.innerHTML = `  
      

My Default Text

    `
    ; const Texts = [ 'My lady, Hello! '.'BuiBuiBui'.'BiliBili'.'Haiwei is NO.1' ] class MyButton extends HTMLElement { constructor () { super(a)const content = template.content.cloneNode(true); const button = content.querySelector('#btn'); const textList = content.querySelector('#text-list'); this.$button = button; this.$message = content.querySelector('#message'); button.addEventListener('click'.(evt) = > { const li = document.createElement('li'); li.innerText = Texts[Math.floor(Math.random() * 4)]; textList.appendChild(li); this.dispatchEvent( new CustomEvent('onClick', { detail: 'Hello fom within the Custom Element'}})))this.attachShadow({ mode: 'open' }).appendChild(content); } get text () { return this.getAttribute('text'); } set text (value) { this.setAttribute('text', value); } static get observedAttributes() { return ['text']; } attributeChangedCallback(name, oldVal, newVal) { this.render(); } render() { this.$message.innerText = this.text; }}window.customElements.define('my-button', MyButton) Copy the code

    Called in the document

    <! DOCTYPEhtml>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
      <title>Document</title>
      <script src="./MyButton.js"></script>
    </head>
    <body>
      <my-button> 
        <p slot="my-text">Another text from outside</p>
      </my-button>
    </body>
    <script>
      const mybutton = document.querySelector('my-button');
      mybutton.addEventListener('onClick'.(value) = > {
        console.log(value)
        mybutton.text = value.detail
      });
    </script>
    </html>
    Copy the code

    At this point, we have a Reusable Web Component.