javascriptclasspropertieslit-element

lit-element property passing to another function/class


I am currently learning how to use lit-elements, and I am also fairly new with web development. I am trying to make a Bluetooth custom element that changes a button text and color depending on if the bluetooth device is connected or not.

In my lit-element I create another class called BTLE, where all the bluetooth is handled. But I have no idea how to pass a lit-element property to this BTLE class. I want my button to get re-rendered when the BTLE class notices a connect or disconnect. If I pass a property through the BTLE class via the constructor and I change the property the button is not re-rendered. If I change the property inside the lit-element class it gets re-rendered.


Solution

  • The idiomatic way to solve this with LitElement is to make the BTLE object emit events when its state changes, and for other objects (like custom elements) that care about its state to listen for those events.

    The easiest way to do that in a web app is to make the BTLE class extend EventTarget, which will give it addEventListener, dispatchEvent, etc. methods. Then, instead of passing callback functions to the BTLE constructor, your custom element will call addEventListener on it to start listening. When the BTLE object's status changes, instead of calling this.callbackFunction(...) it will call this.dispatchEvent(new Event('state-change')) (for example).

    To accomplish this, your custom element will need to define a custom getter and setter so it knows when its bluetoothGatt property changes, because it will need to call addEventListener on the new BTLE. This is the most complex part, so I strongly recommend reading the section "Configure property accessors" in the LitElement docs.

    In the below snippet you can see a working example of how this might look in practice:

    const { LitElement, html } = litElement;
    
    const BluetoothGattState = { Disconnected: 0, Connected: 1 };
    
    // A simple app to test our custom element
    class MyApp extends LitElement {
      render() {
        // Pass the BTLE object to `<bt-indicator>` as a property
        return html`
          <bt-indicator .bluetoothGatt=${new BTLE()}></bt-indicator>
        `;
      }
    }
    customElements.define('my-app', MyApp);
    
    // Our <bt-indicator> custom element
    class BtIndicator extends LitElement {
      static get properties() {
        return {
          bluetoothGatt: { type: Object },
          btState: { type: Number },
        };
      }
    
      constructor() {
        super();
        this._bluetoothGatt = null;
        this._handleBtGattStateChange = this._handleBtGattStateChange.bind(this);
      }
    
      render() {
        return html`Bluetooth status: ${
          this.btState === BluetoothGattState.Connected ? '✅' : '🛑'
        }`;
      }
    
      // A custom setter that calls addEventListener whenever the bluetoothGatt
      // property changes
      set bluetoothGatt(obj) {
        const oldObj = this._bluetoothGatt;
    
        if (oldObj) {
          // Remove event listener from old BTLE
          oldObj.removeEventListener('state-change', this._handleBtGattStateChange);
        }
    
        if (obj) {
          this._bluetoothGatt = obj;
          this.btState = obj.state;
          // Add event listener to new BTLE
          obj.addEventListener('state-change', this._handleBtGattStateChange);
        } else {
          this._bluetoothGatt = null;
        }
    
        // Tell LitElement the property changed so it can re-render
        this.requestUpdate('bluetoothGatt', oldObj);
      }
      
      get bluetoothGatt() {
        return this._bluetoothGatt;
      }
      
      _handleBtGattStateChange(evt) {
        // update this.btState when the BTLE's stage changes
        this.btState = evt.target.state;
      }
    }
    customElements.define('bt-indicator', BtIndicator);
    
    
    // BTLE as a simple EventTarget -- connects/disconnects every 1000ms and
    // dispatches 'state-change'
    class BTLE extends EventTarget {
      constructor() {
        super();
        this.setState(BluetoothGattState.Disconnected);
        
        setInterval(() => {
          this.setState(this.state === BluetoothGattState.Disconnected ? BluetoothGattState.Connected : BluetoothGattState.Disconnected);
        }, 1000);
      }
      
      setState(val) {
        this.state = val;
        this.dispatchEvent(new Event('state-change'));
      }
    }
    <script src="https://bundle.run/lit-element@2.2.1"></script>
    
    <my-app><my-app>