How to Handle Events with LitElement and TypeScript
Tags: litelement, typescript, webcomponents, webdev
Published: 2021-02-11 16:23:12

In [previous posts](), I covered different topics about Web Components and Single-Page Applications using [LitElement](https://lit-element.polymer-project.org/) and [TypeScript](https://www.typescriptlang.org/).

It's well known that LitElement has added good support to manage _Properties_ and _Events_ to write powerful web components. In this post, we'll focus on event handling using a LitElement-based component as a use case using the TypeScript language. 

## The Custom Component

Let's start the Web Component creation using the [Stackblitz](https://stackblitz.com/) editor and define the initial code for our first component:

```ts
// my-button.ts

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

@customElement("my-button")
class MyButton extends LitElement {
  static styles = css`
    :host {
      display: inline-block;
      padding: 10px;
      background: #5fe1ee;
      border-radius: 5px;
      cursor: pointer;
    }
  `;

  @property() label = "Hello LitElement";

  constructor() {
    super();
  }

  render() {
    return html`
      <span>
        ${this.label}
      </span>
    `;
  }
}
```

The previous code creates a custom button definition using the following features from LitElement:

* The `@customElement` decorator allows the component definition using a name for it: `my-button`. It is applied at the class level.
* The `static styles` attribute defines the styles for the component using a tagged template literal(`css`) 
* The `@property` decorator, which allows declaring properties in a readable way.
* The `render` method returns the HTML content through a template literal(`html`). This function will be called any time `label` property changes.

## How to Fire Events

Once the previous code gets rendered in the browser, you'll see a custom button definition as the next screenshot shows:

![Custom button LitElement](//images.ctfassets.net/zojzzdop0fzx/1Re5atnJXNL9n5mbCjiMQV/244cbe7d5d4718d9fd954713200b14ff/custom-button-litelement.png)

However, this button definition doesn't have the ability to fire events yet. Let's add that support updating the `render` function:

```ts
// my-button.ts

  render() {
    return html`
      <span @click=${this.handleClick}>
        ${this.label}
      </span>
    `;
  }

  private handleClick(e: MouseEvent) {
    console.log("MyButton, click", e);
  }
```

The `render` function does use the [lit-html](https://lit-html.polymer-project.org/) `@click` binding into the template. This will allow capturing the _click_ event. In other words, we're adding an event listener in a **declarative way**.

Let's assume the brand new `<my-button></my-button>` component needs to fire the click event. This can be done using the `dispatchEvent` function.

```ts
// my-button.ts

  private handleClick(e: MouseEvent) {
    this.dispatchEvent(new Event("myClick"));
  }
```

### Handling the Event
In a real-world scenario, we'll need to handle (or listen) the new `myClick` event once it gets fired. 

Let's add a container component int the `my-container.ts` file and import the `my-button.ts` definition as follows:

```ts
// my-container.ts

import { LitElement, html, customElement, css } from "lit-element";
import "./my-button";

@customElement("my-container")
class MyContainer extends LitElement {
  static styles = css`
    :host {
      display: block;
    }
  `;

  constructor() {
    super();
  }

  render() {
    return html`
      <my-button @myClick=${this.handleClick} label="Hello LitElement">
      </my-button>
    `;
  }

  private handleClick(e: Event) {
    console.log("MyContainer, myClick", e);
  }
}
```

Let's explain what's happening there:

* The `render` function defines a template literal and makes use of the `my-button` element using `<my-button></my-button>` as if it were part of the HTML vocabulary
  * The `@myClick` attribute sets a function reference to handle the event in a declarative syntax.
  * The `label` attribute sets the text displayed in the button. Anytime it changes, the button will be rendered again.
* The `handleClick` function receives an `Event` object with more information about it. Open your browser's console and feel free to inspect this value.

## How to Fire Custom Events
There are times when the web component needs to send a value or an object (along with its attributes) when it fires the event. if that's the case, you can create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).

Since we're using TypeScript, it would be great to create a model for the data to be passed through the Custom Event. Let's create it in the `my-button.ts` file as:

```ts
// my-button.ts

export interface MyButtonEvent {
  label: string;
  date: string;
}
```

Of course, you can create a separate file for this model. Now, let's update the `handleClick` function as follows:

```ts
// my-button.ts

  private handleClick(e: MouseEvent) {
    const event = new CustomEvent<MyButtonEvent>("myClick", {
      detail: {
        label: this.label,
        date: new Date().toISOString()
      }
    });
    this.dispatchEvent(event);
  }
```

The Custom Event object is created for specifying the data type (`MyButtonEvent`) that will be fired as part of the `detail`. Remember the name of this interface: we'll make use of it next.

### Handling the Custom Event
In a previous section, we've been handling the `myClick` event in the `my-container.ts` file. Let's update it to support our Custom Event.

```ts
// my-container.ts

  private handleClick(e: CustomEvent<MyButtonEvent>) {
    const detail: MyButtonEvent = e.detail;

    console.log("MyContainer, myClick", detail);
  }
```

Pay attention to the event parameter and the type we're using to capture the event details: `CustomEvent<MyButtonEvent>`. This is helpful to capture the emitted object with the right type. Thanks, TypeScript! :-)

![Custom Event Handling with LitElement](//images.ctfassets.net/zojzzdop0fzx/5UPC7GfAt9lrZ4ueH0DV3J/b1b5338265084ed38cbaba6a466a6dd8/custom-event-litelement.png)

## Live Demo
Wanna play around with this code? Just open the Stackblitz editor:

{% stackblitz lit-element-events %}

## Conclusion

LitElement is an excellent alternative to build lightweight web applications since it's based on the Web Components standard, and with the help of TypeScript, we can see more possibilities to build Web Components faster with a good developer experience.

Feel free to reach out on [Twitter](https://twitter.com/luixaviles) if you have any questions. Follow me on [GitHub](https://github.com/luixaviles) to see more about my work.

---

*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*