Skip to content

Instantly share code, notes, and snippets.

@dliebner
Created December 5, 2022 08:58
Show Gist options
  • Save dliebner/71040dbcd97d2eb27aa92befa56ff281 to your computer and use it in GitHub Desktop.
Save dliebner/71040dbcd97d2eb27aa92befa56ff281 to your computer and use it in GitHub Desktop.
GlobalSignalProvider for managing preact signals in Lit
// Requires https://lit.dev/playground/#gist=660427ac6065bed390c438b0eb5b5036
class FollowUserButton extends SignalWatcher(LightElement) {
static properties = {
userId: {state: true},
isFollowingInitial: {state: true},
};
/** @type {String} */
_userId;
constructor() {
super();
/** @type {boolean} */
this.isFollowingInitial;
}
get userId() {
return this._userId;
}
set userId( val ) {
// Unsubscribe from any old signals
this.unsubscribeSignals();
// Hold reference to old value
let oldVal = this._userId;
// Update the internal value
this._userId = val;
// Subscribe to signals
this.subscribeSignals();
// Let component know the value has changed
this.requestUpdate('userId', oldVal);
}
followingUserSignal;
subscribeSignals() {
if( this.userId && !this.followingUserSignal ) {
this.followingUserSignal = GlobalSignalProvider.subscribe(this, GSP.SignalKeys.FollowingUser(this.userId), !!this.isFollowingInitial );
}
}
unsubscribeSignals() {
if( this.userId && this.followingUserSignal ) {
GlobalSignalProvider.unsubscribe(this, GSP.SignalKeys.FollowingUser(this.userId) );
this.followingUserSignal = undefined;
}
}
connectedCallback() {
super.connectedCallback();
this.subscribeSignals();
}
disconnectedCallback() {
super.disconnectedCallback();
this.unsubscribeSignals();
}
/** @param {MouseEvent} e */
clicked(e) {
this.followingUserSignal.value = !this.followingUserSignal.value;
}
render() {
return html`
<button @click=${this.clicked}>${this.followingUserSignal.value ? 'Following' : 'Follow'}</button>
`;
}
}
class GSP_WrappedSignal {
constructor(initialValue) {
this.subscribers = new WeakMap();
this.preactSignal = preactSignals.signal(initialValue);
}
}
class GlobalSignalProvider {
// Use a Map to store the list of subscribers for each signal
static wrappedSignals = new Map();
// Helper function for generating a signal key
static SignalKeys = {
FollowingUser: (userId) => `FollowingUser:${userId}`,
};
// Helper function for getting a signal
/** @return {GSP_WrappedSignal} */
static getWrappedSignal(signalKey) {
return this.wrappedSignals.get(signalKey);
}
// Helper function for creating a signal if it doesn't exist
static createSignalWrapper(signalKey, initialValue) {
let wrappedSignal = this.getWrappedSignal(signalKey);
if( !wrappedSignal ) {
wrappedSignal = new GSP_WrappedSignal(initialValue);
this.wrappedSignals.set(signalKey, wrappedSignal);
}
return wrappedSignal;
}
// Helper function for destroying a signal
static destroyWrappedSignal(signalKey) {
this.wrappedSignals.delete(signalKey);
}
// Function for subscribing to a signal
static subscribe(requester, signalKey, initialValue) {
// Create the signal if it doesn't exist
const wrappedSignal = this.createSignalWrapper(signalKey, initialValue);
// Add the requester to the list of subscribers for the signal
wrappedSignal.subscribers.set(requester, true);
// Return the preactSignal
return wrappedSignal.preactSignal;
}
// Function for unsubscribing from a signal
static unsubscribe(requester, signalKey) {
const wrappedSignal = this.getWrappedSignal(signalKey);
if( !wrappedSignal || !wrappedSignal.subscribers ) {
// Do nothing if the signal doesn't exist or there are no subscribers
return;
}
// Remove the requester from the list of subscribers
wrappedSignal.subscribers.delete(requester);
if( wrappedSignal.subscribers.size === 0 ) {
// Destroy the signal if there are no remaining subscribers
this.destroyWrappedSignal(signalKey);
}
}
}
// Create a shorthand reference to the GobalSignalProvider
const GSP = GlobalSignalProvider;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment