Wednesday, February 13, 2019

Polymer LitElement is Here!



I've been using Polymer to help me write web components for a couple months now. During that time I've been using Polymer's base class PolymerElement which among other things provides DOM templating and data binding.

Just the other day the Polymer team announced the official release of their new base class called LitElement. It is provides a more lightweight and simpler way to create web components as compared to the PolymerElement.

I decided to give it a try and this is what I learned.

First of all I really like the simplicity of the LitElement programming model. It's very lightweight and easier to understand than the old PolymerElement way of doing things. If you've ever used React you will find that using LitElement is almost exactly like creating components in React. In fact I think the Polymer team borrowed heavily from React (or maybe ripped them off depending on your point of view).

The two-way data binding of PolymerElement is gone and replaced by one-way data binding, a la React. The old two-way data binding felt more Angular, while the new on-way feels, as I said, more like React. You can't rely on changes to propagate up anymore. Now you need to use events to notify parent elements that something has changed. This is a little more work, but in the long term probably more simple and reliable.

PolymerElement's two-way data binding was sometimes complex to understand and required you to use all sorts of extra configuration to make it work. LitElement's one-way data binding is just simple and easy to understand. I'm not sure I would even call it data binding, really. Your element has properties and when they change your element is re-rendered.

There is the big difference, I think. In PolymerElement you had a static accessor where you defined your HTML template with data binding embedded into it. With LitElement you now have a render() method that gets called any time a property of your element changes. The render() method completely rebuilds the DOM structure of your element. Instead of embedded data binding you build a string that represents your DOM using template literals accessing your properties directly.

For example, here's what a data binding looks like in PolymerElement where "name" is a property of the element.

1
2
3
4
5
    static get template() {
        return html`
            <div>Hello, <span>[[name]]!</span></div>
        `;
    }

And here's what it looks like in LitElement.

1
2
3
4
5
    render() {
        return html`
            <div>Hello, <span>${this.name}!</span></div>
        `;
    }

Notice how we are building a string inserting "this.name" property as an embedded expression in the template literal rather than using a static binding "[[name]]".

This works because behind the scenes the LitElement creates get and set accessor methods for the properties of your element. Whenever a property changes it calls the render method so that the template string gets rebuilt. Then it figures out what changed and updates that portion of the light DOM on the page.

The great thing about using template literals is that you can now use any kind of JavaScript expression to build your DOM template, giving you a lot more power.

The consequence of this is that element properties must now be immutable. If you use React you are probably used to this. If one of your properties is an object or array you can't change an element in it and expect LitElement to notice that change and call render(). You will need to make a new copy of the object or array and change the value you want. If you want to know why this is better in the long run I suggest going through the React tutorial. They explain it in detail.

This also means that changes to properties in child elements will not automagically propagate up to the parent's properties. If you want to notify the parent of a change you will need to use custom events. Again, I suggest going through the React tutorial if you are not familiar with this way of doing things. (I told you they ripped off React :-p)

There is also a new way to bind properties to DOM element. For event handlers you use an @ before the event name and then an embedded expression using the name of the handler function. To define boolean attributes use a question mark. To bind to properties use a dot.


`<input type="text"
    @keyup="${this.keyPressed}"
    .value="${this.name}"
    ?hidden="${!this.isVisible}">`

In all cases you use embedded expressions for all of these bindings, even event handlers.

Using TypeScript

If you're like me and prefer using type annotations in your code, LitElement has support for TypeScript. It also allow you to use decorators to define your custom element and its properties. Use the customElement() decorator on your class and the property() decorator on properties. This will set up your element and properties for you making your code more terse.

1
2
3
@customElement('my-element')
export class MyElement extends LitElement {
  @property({type : String})  prop1 = 'Hello World';

I tried this but was having trouble getting it to work. In general I was having trouble getting the LitElement npm package to work nicely with TypeScript. The TS files in the npm package would not compile correctly because there was something missing that it relies on. This also affected the code completion in VS Code. [Edit] I did get type definitions and decorators to work correctly. Problem was that my compiler was not up to the version required for LitElement, which I think is 3.2. After installing latest everything worked fine. Also, you will need to set experimentalDecorators to true in your tsconfig file if you want to use decorators.

I'm really hoping TS support gets better over time. I didn't have these issues using PolymerElement.

Conclusion

In some ways LitElement feels like a step back because it's so much more primitive than PolymerElement was. But I feel like this is a good thing because it makes everything a whole lot simpler to understand and reduces complexity in your apps. I suggest you go ahead and start using LitElement now and hopefully TypeScript support will get better (or I will figure out what I'm doing wrong) in time.

Start learning LitElement here: https://lit-element.polymer-project.org/guide

Since LitElement is so much like React, and the documentation is limited for LitElement, I also suggest reading the React tutorial. https://reactjs.org/docs/hello-world.html

No comments:

Post a Comment