Web Components Integration using LitElement and TypeScript
Tags: litelementtypescript
Published: 2020-10-05 19:48:06

---
title: Web Components Integration using LitElement and TypeScript
published: true
date: 2020-10-05 15:17:54 UTC
tags: litelementtypescript
canonical_url: https://www.thisdot.co/blog/web-components-integration-using-litelement-and-typescript
---

In my previous posts, I explained [how to create a project based on LitElement and TypeScript](https://labs.thisdot.co/blog/getting-started-with-litelement-and-typescript) from scratch. Also, I added the [routing management](https://labs.thisdot.co/blog/routing-management-with-litelement) and explained how to have more control over the navigation lifecycle.

Let's deep dive over the Web Components creation to understand how to reuse them, respond to any property change, and dispatch custom events.

### Writing a Web Component

Let's define a base template to display an overview of every blog post in our web application.

```
<div class="blog-card">
  <div class="blog-description">
    <h1>Title</h1>
    <h2>Author</h2>
    <p>
      Brief Description
    </p>
    <p class="blog-footer">
      <a class="blog-link">Read More</a>
    </p>
  </div>
</div>
```

With this template, we should expect to draw a simple _card_ to display the post content. It will define the internal DOM for our new component.

Let's create the Web Component using [LitElement](https://lit-element.polymer-project.org/) and the `@customElement` decorator:

```
// blog-card.ts

import { LitElement, html, customElement, css } from 'lit-element';
import { Post } from './post';

@customElement('blog-card')
export class BlogCard extends LitElement {
  static styles = css`
  .blog-card {
    margin: 20px;
    display: flex;
    flex-direction: column;
    margin-bottom: 15px;
    background: white;
    border-radius: 5px;
    overflow: hidden;
    border-radius: 10px;
  }
  .blog-description {
    padding: 20px;
    background: white;
  }
  .blog-footer {
    text-align: right;
  }
  .blog-link {
    color: #008cba;
  }
  h1 {
    margin: 0;
    font-size: 1.5rem;
  }
  h2 {
    font-size: 1rem;
    font-weight: 300;
    color: #5e5e5e;
    margin-top: 5px;
  }
  `;

  render() {
    return html`
      <div class="blog-card">
        <div class="blog-description">
          <h1>Title</h1>
          <h2>Author</h2>
          <p>
            Brief Description
          </p>
          <p class="blog-footer">
            <a class="blog-link">Read More</a>
          </p>
        </div>
      </div>
    `;
  }
}
```

This component is ready to be rendered. However, it cannot be reused since the content will remain always the same. We need a way to configure this component so that it can display different content for each instance of it(That should be ideal, right?).

### Adding Properties

The TypeScript-way to declare the element's properties is as follows:

```
// blog-card.ts

@customElement('blog-card')
export class BlogCard extends LitElement {

  @property({ type: String }) postTitle?: string;
  @property({ type: String }) author?: string;
  @property({ type: String }) description?: string;
}
```

That looks enough to configure our new component and being able to display an overview of a blog post. However, what will happen if we decide to add other attributes? The properties list will be growing in number and maybe it's not the best way to handle this scenario.

The other option is define a `Post` model through a TypeScript Interface:

```
// post.ts
export interface Post {
  id: number;
  title: string;
  author: string;
  description: string;
}
```

Then, let's define a single property that expects a **Post** object:

```
// blog-card.ts

@customElement('blog-card')
export class BlogCard extends LitElement {

  @property({ type: Object }) post?: Post;
}
```

### Binding Properties

It's time to improve the `render` function and create property bindings into the template.

```
// blog-card.ts

  render() {
    return html`
      <div class="blog-card">
        <div class="blog-description">
          <h1>${this.post?.title}</h1>
          <h2>${this.post?.author}</h2>
          <p>
            ${this.post?.description}
          </p>
          <p class="blog-footer">
            <a class="blog-link">Read More</a>
          </p>
        </div>
      </div>
    `;
  }
```

The `@property` declaration(defined before) will render the template every time the given property changes.

### Adding Events

There are different ways to add event listeners for our Web Components. In this case, we can use a _declarative event listener_ using `@event` notation:

```
  render() {
    return html`
      <div class="blog-card">
        <div class="blog-description">
          <h1>${this.post?.title}</h1>
          <h2>${this.post?.author}</h2>
          <p>
            ${this.post?.description}
          </p>
          <p class="blog-footer">
            <a class="blog-link" @click="${this.handleClick}">Read More</a>
          </p>
        </div>
      </div>
    `;
  }

  private handleClick() {
    this.dispatchEvent(
      new CustomEvent('readMore', { detail: this.post })
    );
  }
```

The event listener `@click="${this.handleClick}"` will be added once the template gets rendered in the browser. The click action will be handled by the `handleClick` function.

This function will fire an event from our Lit-based web component. The `CustomEvent` allows an object `Post` to be propagated along with it.

### Parent-Child Component Communication

The `blog-card` component is ready to be integrated into the application.

[![parent-child](https://images.ctfassets.net/zojzzdop0fzx/6Yl7OjRgq3oPZYOJc1zmyl/2a13def0ad3ea8f4c494e50d43eb0647/parent-child.jpg)](https://images.ctfassets.net/zojzzdop0fzx/6Yl7OjRgq3oPZYOJc1zmyl/2a13def0ad3ea8f4c494e50d43eb0647/parent-child.jpg)

Let's think in the _Blog Posts_ page as the **parent** and the _Blog Card_ as the **child component** :

```
// blog-posts.ts

import { POSTS } from './data';
import { Post } from './post';

@customElement('lit-blog-posts')
export class BlogPosts extends LitElement {

  @property({ type: Array }) blogPosts?: Post[];

  constructor() {
    super();
  }

  render() {
    return html`
      <h2>Blog Posts</h2>
      ${this.blogPosts?.map(
        post => html`<blog-card .post="${post}"></blog-card>`
      )}
    `;
  }

  firstUpdated() {
    this.blogPosts = POSTS;
  }
}
```

The parent component defines a property to store a set of blog posts using `@property({ type: Array }) blogPosts?: Post[];`.

If you have the Web component template and want to bind properties, take into account the following rules:

- Text content: `<p>${...}</p>`
- Attribute: `<p id="${...}"></p>`
- Boolean attribute: `?disabled="${...}"`
- Property: `.value="${...}"`
- Event handler: `@event="${...}"`

The [firstUpdated](https://lit-element.polymer-project.org/guide/lifecycle#firstupdated) function will be called after the element's DOM has been updated the first time. In a real-world scenario, the app would need to perform an HTTP call to get the data.

For this example, let's load the data from `data.ts` file:

```
// data.ts

import { Post } from './post';

export const POSTS: Post[] = [
  {
    id: 0,
    title: 'Web Components Introduction',
    author: 'Luis Aviles',
    description:
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit...',
  },
  {
    id: 1,
    title: 'LitElement with TypeScript',
    author: 'Luis Aviles',
    description:
      'Sed felis nisi, consectetur sed ipsum dignissim, semper porta risus...',
  },
  {
    id: 2,
    title: 'Navigation and Routing with Web Components',
    author: 'Luis Aviles',
    description:
      'Ut ipsum arcu, sodales aliquet nisi iaculis, faucibus varius mauris...',
  },
];
```

### Listen to Child Events

The parent component is ready to display the blog posts. However, it would be great to listen every time the "Read More" link has been clicked and take the control from the Blog Posts page:

```
// blog-posts.ts

  firstUpdated() {
    this.blogPosts = POSTS;
    this.addEventListener('readMore', event => {
      const post = (event as CustomEvent).detail as Post; //event.detail has a the Post object
      Router.go(`/blog/posts/${post.id}`); // Get the Post id and redirect
    });
  }
```

The event listener will be executed after the first paint. This way of adding them can be useful when you're adding several listeners.

This should be the final result:

[![blog-posts-screenshot](https://images.ctfassets.net/zojzzdop0fzx/5c4S86rBar0rMqQkoZJHg1/7b3b6681306bd28cbffc1da20a6543c5/blog-posts-screenshot.png)](https://images.ctfassets.net/zojzzdop0fzx/5c4S86rBar0rMqQkoZJHg1/7b3b6681306bd28cbffc1da20a6543c5/blog-posts-screenshot.png)

### Source Code Project

Find the complete project in this GitHub repository: [https://github.com/luixaviles/litelement-website](https://github.com/luixaviles/litelement-website). Do not forget to give it a star ⭐️ and play around with the code.

You can follow me on [Twitter](https://twitter.com/luixaviles) and [GitHub](https://github.com/luixaviles) to see more about my work.