Lit Integration
@routier/lit provides LiveQueryController, a Lit reactive controller that follows a Routier live query and re-renders its host when the result changes. It relies only on Lit's ReactiveControllerHost interface, so it also works in any web component that implements that interface.
Installation
bash
npm install @routier/lit
# peer dependency
npm install litLiveQueryController
ts
new LiveQueryController<T>(host, { query });
new LiveQueryController<T, TArgs>(host, { args: () => [...], query: (args) => query });controller.state holds a discriminated union. Narrow on status before reading data or error:
status | Fields |
|---|---|
"pending" | loading: true |
"success" | data: T, isSuccess: true |
"error" | error: Error, isError: true |
- Connect and disconnect. The query runs when the host connects and stops when it disconnects. Reconnecting starts again from
pending. - Args. With
args, the controller compares the returned values before every host update, usingObject.ison each one. If any changed, it stops the previous query and runsquerywith the new values. Withoutargs, the query never re-runs while connected. - Updates. Every result calls
host.requestUpdate().
Example
ts
import { LitElement, html } from "lit";
import { LiveQueryController } from "@routier/lit";
import { store, type Product } from "../../home/inventory";
const pageSize = 6;
export class ProductList extends LitElement {
static override properties = { page: { type: Number } };
page = 1;
private rows = new LiveQueryController<Product[], readonly [number]>(this, {
args: () => [this.page] as const,
query: ([page]) => (onResult) =>
store.products
.sort((p) => p.name)
.skip((page - 1) * pageSize)
.take(pageSize)
.subscribe()
.toArray(onResult),
});
override render() {
const state = this.rows.state;
if (state.status === "pending") {
return html`<p>Loading…</p>`;
}
if (state.status === "error") {
return html`<p>${state.error.message}</p>`;
}
return html`
<ul>${state.data.map((product) => html`<li>${product.name}: ${product.stock}</li>`)}</ul>
<button ?disabled=${this.page === 1} @click=${() => this.page--}>‹</button>
<button @click=${() => this.page++}>›</button>
`;
}
}
customElements.define("product-list", ProductList);store and Product come from a shared module, the same one the Vue page uses.