LitElement properties: @property vs @internalProperty
Tags: litelement, typescript, webcomponents, webdev
Published: 2021-02-18 00:22:16

[LitElement](https://lit-element.polymer-project.org) 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.

In this article, I will explain the practical use of `@property` and `@internalProperty` decorators using a TypeScript implementation.

## The Problem
Let's suppose you're building a small application that needs to display a list of _Users_. When you select any of them, other details should be displayed. In this case, it's the _Address_ information.

![LitElement properties: @property vs @internalproperty](//images.ctfassets.net/zojzzdop0fzx/6Q4CZ3WViPrOMfyDlXoEoP/50cd445f57b423e5ba6ca0f782628e53/litelement-property-vs-internalproperty.png)

Next, let's define the data model and a dataset before implementing the web components using LitElement.

## Data Modeling and Dataset

### The Data Model
Let's rely on TypeScript _interfaces_ and static typing to have the model ready.

```ts
// model.ts
export interface User {
  id: number;
  name: string;
}

export interface Address {
  country: string;
  state: string;
  city: string;
  street: string;
  zipCode: number;
}
```

As you may note, the `Address` interface will display a set of attributes that represents a complete address.

### The Source of Data
To make the testing easier, let's create a dataset of entities that matches the previous model.

```ts
// data.ts
import { Address } from "./model";
import { User } from "./model";

export const users: User[] = [
  {
    id: 0,
    name: "Donald Mayfield"
  },
  {
    id: 1,
    name: "Jill J. Fritz"
  },
  {
    id: 2,
    name: "Terry Buttram"
  }
];

export const address: Address[] = [
  {
    street: "2180 BELLFLOWER",
    country: "USA",
    state: "AL",
    city: "Madison",
    zipCode: 35064
  },
  {
    street: "845 ODOM ROAD, SUITE 200",
    country: "USA",
    state: "CA",
    city: "Los Angeles",
    zipCode: 90720
  },
  {
    street: "9025 QUEENS BLVD",
    country: "USA",
    state: "NY",
    city: "Queens",
    zipCode: 11355
  }
];
```
The relationship between the `users` and `address` is simple: The User `id` matches with the position of the `address` array.

## Using LitElement Properties
LitElement provides different ways to manage the properties. As the documentation says:

> LitElement manages your declared properties and their corresponding attributes.

This can be done using either a static `properties` field or using decorators. Let's take the _TypeScript-way_ using the power of decorators.

We'll understand the "properties management" better in the practice through the next sections.

### Creating the MainViewer Container
Let's create a class to contain our first component. In this case, we'll define a _container_ component that will be in charge of displaying both the list of users and their details.

```ts
import {
  LitElement,
  html,
  customElement,
  css,
  internalProperty
} from "lit-element";
import { users } from "./data";
import { User } from "./model";

@customElement("main-viewer")
class MainViewer extends LitElement {
  static styles = css`
    :host {
      display: block;
    }
  `;

  users: User[] = [];
  userId?: number;

  constructor() {
    super();
  }

  render() {
    return html`
      <div>
        <h1>User Address Viewer</h1>
        <span>Select a User to see the Address:</span>
        <ul>
          ${this.users.map(
            user => html`
              <li>
                <a href="#" @click="${() => this.viewAddress(user.id)}"
                  >${user.name}</a
                >
              </li>
            `
          )}
        </ul>
      </div>
    `;
  }

  async connectedCallback() {
    super.connectedCallback();
    this.users = await this.getUsers();
  }

  private getUsers() {
    // in the real-world you'll get the data from a service, file, etc.
    return new Promise<User[]>((resolve, reject) => resolve(users));
  }

  private viewAddress(id: number) {
    this.userId = id;
  }
}
```

Let's explain what is happening in this component so far:

* The `static styles` attribute defines the styles for the component using a tagged template literal (`css`).
* It defines two **class properties**: `users` to "store" the set of Users to be displayed in the main list. Also, the `userId` will contain a reference of the selected User identifier (this value will be changed every time you select a different user).
* The `render` method returns the HTML content through a template literal (`html`). This function will be called any time a **component property changes**.
* The `connectedCallback` function makes a call to `getUsers` in order to get the initial data when the component is added to the document's DOM.
* The `getUsers` function should perform an asynchronous call (in a real-world scenario) to retrieve the data.
* The `viewAddress` function receives an `id` of the object selected once the user performs a _click_ action over the link. Then, the class property `userId` will be updated.

The previous code is perfectly fine. However, **it won't display any data yet**! We'll only see the title and a blank section on the page and no list of users rendered.

### Using the @internalProperty Decorator
In order to fix this rendering issue, we should make sure to _trigger_ an update cycle for the web component. 

Think for a while about **when** the update cycle should be performed: every time you **change** the list of `users`! However, this cannot be done every time you update any variable or attribute from your class. Instead, you should mention which attributes are "keys" to perform the _rendering_ operation or keep your component _updated_. We'll call **LitElement properties** to these key attributes.

Once you have identified them, we'll need to declare these attributes as **properties**:

```ts
// main-viewer.ts

@customElement("main-viewer")
class MainViewer extends LitElement {
  // ...

  @internalProperty() users: User[] = [];
  @internalProperty() userId?: number;

  constructor() {
    super();
  }

  // ...
}
```

Why use `@internalProperty` in this case? 
* For this `MainViewer` component, we don't need to reference either `users` or `userId` from outside the component.
* From the previous point, we can consider `users` and `userId` attributes as _private_ or _protected_.

In other words, LitElement observes and "reacts" to these property changes so the template gets rendered/updated automatically.

### Using the @property Decorator

#### Creating the AddressViewer Component
Before creating the _child_ component, let's think about how we are going to use it from the container. In terms of web components, let's suppose we'll need the following element and attribute:

```html
<address-viewer .userId=${userId}></address-viewer>
```

That means the new `address-viewer` component will need to "receive" the `userId` value to be able to retrieve the respective Address. The `.userId=${userId}` notation applies a one-way data binding to a property, meaning the `AddressViewer` component will have the `userId` as a **public property**.

#### @property vs @internalProperty decorators

Let's take the previous consideration into account and create a new file `address-viewer.ts`:

```ts
import {
  LitElement,
  html,
  property,
  customElement,
  css,
  internalProperty
} from "lit-element";
import { address } from "./data";
import { Address } from "./model";

@customElement("address-viewer")
class AddressViewer extends LitElement {
  static styles = css`
    :host {
      display: block;
    }

    table {
      border-collapse: collapse;
      width: 100%;
    }

    td,
    th {
      border: 1px solid gray;
      text-align: left;
      padding: 5px;
    }
  `;

  @property({ type: Number }) userId: number;
  @internalProperty() userAddress?: Address;

  constructor() {
    super();
  }

  render() {
    if (this.userAddress === undefined) {
      return html``;
    }

    return html`
      <table>
        <tr>
          <th>Country</th>
          <th>State</th>
          <th>City</th>
          <th>Street</th>
          <th>Zip Code</th>
        </tr>
        <tr>
          <td>${this.userAddress.country}</td>
          <td>${this.userAddress.state}</td>
          <td>${this.userAddress.city}</td>
          <td>${this.userAddress.street}</td>
          <td>${this.userAddress.zipCode}</td>
        </tr>
      </table>
    `;
  }

  update(changedProperties: Map<string, unknown>) {
    if (changedProperties.has("userId")) {
      const oldValue = changedProperties.get("userId") as number;
      console.log("userId updated, newVal", this.userId, "oldVal", oldValue);
      this.loadAddress(this.userId);
    }
    super.update(changedProperties);
  }

  private async loadAddress(id: number) {
    this.userAddress = await this.getAddress(id);
  }

  private getAddress(id: number) {
    return new Promise<Address>((resolve, reject) => resolve(address[id]));
  }
}
```

Again, let's take a closer look at the previous source code:

* The `userId` attribute is defined as a **public property** for the component using `@property` decorator.
* The `userAddress` property doesn't need to be public property for the component. Instead, it's defined using `@internalProperty` decorator to _trigger_ the update cycle once it gets changed.
  * Initially, It's `undefined`
* The `render` method returns the HTML content to be rendered every time a property is changed.
  * Note the function will return a meaningful template only once the `userAddress` contains the required object.
* The `update` function reflects property values and calls the `render` function.
  * It receives a `Map` with the properties that have been changed.
  * It verifies if the `userId` property has been changed, and then performs a call to `loadAddress`.
  * Whenever you override this method, you'll need to call `super.update()` to render your template.
* The `loadAddress` function takes the new value for `userId` and  calls the utility function to retrieve the Address object. 

The brand-new `AddressViewer` component is ready, and we'll need to use it as a _child_ component in the main container. Let's update the `render` function as follows:

```ts
// main-viewer.ts

// Let's import the <address-viewer> definition
import "./address-viewer";

@customElement("main-viewer")
class MainViewer extends LitElement {
  //...

  render() {
    return html`
      <div>
        <h1>User Address Viewer</h1>
        <span>Select a User to see the Address:</span>
        <ul>
          ${this.users.map(
            user => html`
              <li>
                <a href="#" @click="${() => this.viewAddress(user.id)}"
                  >${user.name}</a
                >
              </li>
            `
          )}
        </ul>
        <address-viewer .userId=${this.userId}></address-viewer>
      </div>
    `;
  }

 // ... 
}
```

Again, pay attention to the one-way data binding `.userId=${this.userId}` that will trigger a _rendering_ operation over the `<address-viewer>` component whenever the `userId` gets changed. This is magic, right?

## Live Demo
Want to play around with this code? Open the Stackblitz editor:

{% stackblitz lit-element-property-internalproperty %}

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).*