Share Data Between Web Components

Subscribe to my newsletter and never miss my upcoming articles

If you are used to frameworks like Angular, React, or Vue, you know how to share data between your components, right? For example in the case of React, we have props which is a nice and straight-forward way of passing data between components.

Web Components, or rather custom elements, have a similar concept called attributes and properties. However, they are slightly different to use and not as comfortable as React props, unfortunately.

Let’s have a look at different ways on how we can pass data between our components, shall we?

#1 Using Attributes

The easiest way to pass data but works only with strings.

First, let's create our parent component - my-component which has a child component - child-component:

//MyComponent.js
import { LitElement, html, css } from 'lit-element';

export class MyComponent extends LitElement {
  constructor() {
    super();
    this.title = 'Hello!';
  }

  render() {
    return html`<child-component title=${this.title}></child-component>`;
  }
}

Here the parent component is passing down the data into the child using the title attribute. The child component has to access this data from its attribute which can be easily done using getAttribute() function.

//ChildComponent.js
import { LitElement, html, css } from 'lit-element';

export class ChildComponent extends LitElement {
    connectedCallback() {
        super.connectedCallback();
        this.title = this.getAttribute("title");
    }

    render() {
        return html`<h1>${this.title}</h1>`;
    }
}

#2 Using Properties

Great for getting complex data in a custom element.

A property is like a variable which is bound to an object. To attach a property to our child component first we need to make sure that the child component has been rendered or not. Else the querySelector() to select the child component will return null.

So this time we will do the operations inside the firstUpdated() instead of the constructor() as the former is invoked after render() & the latter is invoked before render().

//MyComponent.js
import { LitElement, html, css } from 'lit-element';

export class MyComponent extends LitElement {
  firstUpdated() {
    let obj = {
      msg: "Hello!",
      from: "Parent Component",
      to: "Child Component",
      date: Date()
    };
    const shadow = this.shadowRoot;
    const childComponent = shadow.querySelector("child-component");
    childComponent.data = obj;
  }

  render() {
    return html`<child-component> </child-component>`;
  }
}

Now we have to declare our child component’s properties using a static properties field & then we can access the passed data.

//ChildComponent.js
import { LitElement, html, css } from 'lit-element';

export class ChildComponent extends LitElement {
    static get properties() {
        return {
            data: {type: Object}
        }
    }

    render() {
        console.log(this.data);
        return html`<p>${this.data.msg}<p>`;
    }
}

Web Console output: {msg: "Hello!", from: "Parent Component", to: "Child Component", date: "Mon Dec 07 2020 16:32:19 GMT+0530 (India Standard Time)"}

#3 Using Events

Using attributes and properties is fine and all, but what if you want to react based on certain events? Just like any HTML element, our custom element can emit custom events for us to listen to.

Let's emit a custom event from our parent component using dispatchEvent() say for example sending some data to the child component on every button click.

//MyComponent.js
import { LitElement, html, css } from 'lit-element';

export class MyComponent extends LitElement {
  firstUpdated() {
    this.count = 0;
    const shadow = this.shadowRoot;
    const myButton = shadow.querySelector("button");
    myButton.onclick = (e) => {
      this.count++;
      this.dispatchEvent(new CustomEvent("clicked",{
        detail: this.count //data
      }));
    }
  }

  render() {
    return html`<button>Click me!</button>
    <child-component> </child-component>`;
  }
}

Now we just have to listen to this custom event inside our child component using addEventListener() & add a callback function to it. This function receives the event object as the only parameter, and part of this object is the property detail, in which we can access the number of times someone has clicked the button.

//ChildComponent.js
import { LitElement, html, css } from 'lit-element';

export class ChildComponent extends LitElement {
    connectedCallback() {
        super.connectedCallback();
        const parent = document.querySelector("my-component");
        parent.addEventListener("clicked", (e) => {
            console.log(e.detail);
        });
    }
}

Conclusion

In this article, I had the pleasure to introduce three different ways of passing data between the components. What method you choose to work with should depend on what you need to achieve. It’s not uncommon to combine a few or all of these techniques in one application.

No Comments Yet