Web Components

Jan 29, 2020

LitElement

with

Adrian Fâciu

Web Components

@adrianfaciu

@adrianfaciu

3 Browser APIs

  • Custom elements

  • Shadow DOM

  • HTML templates

  • define new HTML tags

  • composition

  • encapsulation

@adrianfaciu

Browser support

@adrianfaciu

@adrianfaciu

webcomponents.org

Creating web components

@adrianfaciu

@adrianfaciu

  • browser APIs

  • Stencil.js
  • LitElement

LitElement

@adrianfaciu

A simple base class for creating fast, lightweight web components

@adrianfaciu

HTML Templates

@adrianfaciu

@adrianfaciu

// Create spans
var wrapper = document.createElement('span');
wrapper.setAttribute('class','wrapper');

var icon = document.createElement('span');
icon.setAttribute('class','icon');
icon.setAttribute('tabindex', 0);

var info = document.createElement('span');
info.setAttribute('class','info');

shadow.appendChild(wrapper);
wrapper.appendChild(info);

Different solutions

@adrianfaciu

@adrianfaciu

<form #itemForm="ngForm"
      (ngSubmit)="onSubmit(itemForm)">
  <label for="name"
    >Name <input class="form-control"
                 name="name"
                 ngModel
                 required />
  </label>
  <button type="submit">Submit</button>
</form>

<div [hidden]="!itemForm.form.valid">
  <p>{{ submitMessage }}</p>
</div>

@adrianfaciu

const myTemplate = (
  <>
    <form onSubmit={handleSubmit}>
      <label for="name"
        >Name <input class="form-control"
                     name="name"
                     value={inputvalue} 
                     onChange={handleChange}
                     required />
      </label>
      <button type="submit">Submit</button>
    </form>

    <div hidden={isValid}>
      <p>{submitMessage}</p>
    </div>
  </>
);

lit-html

@adrianfaciu

Template literals

@adrianfaciu

@adrianfaciu

`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`

@adrianfaciu

tag`string text ${expression} string text`

lit-html

@adrianfaciu

import { html } from 'lit-html';

const myTemplate = html`<div>Hello World</div>`;

lit-html

@adrianfaciu

import { html } from 'lit-html';

let myTemplate = (data) => html`
  <h1>${data.title}</h1>
  <p>${data.body}</p>`;

@adrianfaciu

Binding Types

const myTemplate = (data) => 
  html`<div class=${data.cssClass}>${data.text}</div>`;

@adrianfaciu

Binding Types

const myTemplate = (data) => 
  html`<div ?disabled=${data.active}>Item is active</div>`;

@adrianfaciu

Binding Types

const myTemplate = (data) => 
  html`<my-list .listItems=${data.items}></my-list>`;

@adrianfaciu

Event Listeners

const myTemplate = () => 
  html`<button @click=${clickHandlerFn}>Click Me!
</button>`;

const clickHandlerFn = (e) => 
    console.log('clicked!');

const clickHandlerObj = {
  handleEvent(e) { 
    console.log('clicked!');
  },
};

Nested templates

@adrianfaciu

@adrianfaciu

const myHeader = html`<h1>Header</h1>`;

const myPage = html`
  ${myHeader}
  <div>Here's my main page.</div>
`;

array, map, ternary

@adrianfaciu

@adrianfaciu

const items = {
  a: 1,
  b: 23,
  c: 456,
};

const list = () => 
  html`items = ${Object.entries(items)}`;

@adrianfaciu

html`
  <ul>
    ${items.map((i) => html`<li>${i}</li>`)}
  </ul>
`;

@adrianfaciu

html`
  ${user.isloggedIn
      ? html`Welcome ${user.name}`
      : html`Please log in`
  }
`;

render()

@adrianfaciu

@adrianfaciu

import { html, render } from 'lit-html';

// A lit-html template uses the `html` template tag:
let sayHello = (name) => html`<h1>Hello ${name}</h1>`;

// It's rendered with the `render()` function:
render(sayHello('World'), document.body);

// And re-renders only update the data that changed
// without VDOM diffing!
render(sayHello('Everyone'), document.body);

Directives

@adrianfaciu

@adrianfaciu

  • asyncAppend / asyncReplace

  • cache

  • repeat

  • guard

  • until

  • unsafeHTML

@adrianfaciu

Until

import { html, render } from 'lit-html';
import { until } from 'lit-html/directives/until.js';

const fetchData = () => 
  new Promise(resolve => 
              setTimeout(() => resolve(50), 1000));

const template = () => html`
  Count: <span>${until(fetchData(), html`Loading`)}</span>
`

render(template(), document.body);

@adrianfaciu

Repeat

import { repeat } from 'lit-html/directives/repeat';

const myTemplate = () => html`
  <ul>
    ${repeat(items, (i) => i.id, (i, index) => html`
      <li>${index}: ${i.name}</li>`)}
  </ul>
`;

@adrianfaciu

unsafeHTML

import {unsafeHTML}
  from 'lit-html/directives/unsafe-html.js';

const markup = '<div>Some HTML to render.</div>';

const template = html`
  Look out, potentially unsafe HTML ahead:
  ${unsafeHTML(markup)}
`;

LitElement

@adrianfaciu

@adrianfaciu

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

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  @property() name = 'World';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}

@adrianfaciu

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

@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
  @property() name = 'World';

  render() {
    return html`<p>Hello, ${this.name}!</p>`;
  }
}
<simple-greeting name="Everyone"></simple-greeting>

Decorators

@adrianfaciu

@property

@customElement

@query

@eventOptions

Properties

@adrianfaciu

Properties

@adrianfaciu

export class MyElement extends LitElement {
  @property({ type: String }) prop1 = '';
}

Properties

@adrianfaciu

export class MyElement extends LitElement {
  // properties getter
  static get properties() {
    return { 
      prop1: { type: String }
   };
  }
  
  constructor() {
    // Always call super() first
    super();
    
    this.prop1 = 'Hello World';
  }
}

Properties

@adrianfaciu

<my-element 
  prop1="hello world"
  prop2="5"
  prop3
  prop4='{"stuff":"hi"}'
  prop5='[1,2,3,4]'></my-element>

Events

@adrianfaciu

Events

@adrianfaciu

render() {
  return html`
    <button @click="${this.handleClick}">`;
}

Events

@adrianfaciu

this.addEventListener('click', this.handleClick);

this

@adrianfaciu

Fire events

@adrianfaciu

let click = new Event('click');
this.dispatchEvent(click);

let event = new CustomEvent('my-event', {
      detail: {
        message: 'Something important happened'
      }
    });
this.dispatchEvent(event);

Lifecycle

@adrianfaciu

Lifecycle

@adrianfaciu

  1. a property is set

  2. check if update is needed (if needed, request one)

  3. perform the update

  4. resolve a Promise (update is complete)

 

Callbacks

@adrianfaciu

  • connectedCallback

  • disconnectedCallback

  • attributeChangedCallback

Styles

@adrianfaciu

Styles

@adrianfaciu

import { LitElement, css, html }
  from 'lit-element';

class MyElement extends LitElement {
  static get styles() {
    return css`
      div { color: red; }
    `;
  }
  render() { 
    return html`
      <div>I'm styled!</div> 
    `;
  }
}

Why LitElement?

@adrianfaciu

  • fast

  • declarative

  • lightweight

@adrianfaciu

slides.com/adrianfaciu/litelement