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