UNPKG

class-signals

Version:
133 lines (127 loc) 5.1 kB
import { IActivable } from "./activable.interface.ts" import { MononameEventTarget } from "./feature.mononame-event-target/mononame-event-target.class.ts" import { SubscribableSignal } from "./feature.subscriptions/subscribable-signal.class.ts" import { Subscriber, ISubscribeOptions, IUnsubscribeOptions } from "./signals.module.ts" /** * A fully reactive signal that allows both emitting and observing events. * * `Signal<T>` is a reactive primitive designed for class-based architectures. * It allows internal or external code to both **subscribe to events** and **trigger them** directly via {@link activate}. * * Subscribers can be either: * - functions receiving the payload directly, or * - objects with a `handleSignal(payload: T)` method. * * This makes it ideal for implementing simple observable state, reactive messaging, or event-driven communication. * * @template T - The type of the payload passed to subscribers when the signal is activated. * * @example * class Counter { * public readonly onChange = new Signal<number>() * * increment() { * this.onChange.activate(this.value++) * } * } * * const counter = new Counter() * counter.onChange.subscribe(value => { * console.log("Counter changed to:", value) * }) */ export class Signal<T = never> extends SubscribableSignal<T> implements IActivable<T> { /** Internal event target used to dispatch and manage the signal's event listeners. */ #eventTarget = new MononameEventTarget<T>() constructor() { const eventTarget = new MononameEventTarget<T>() super(eventTarget) this.#eventTarget = eventTarget } /** * Activates the signal, notifying all current subscribers. * * This method delivers the given `detail` payload to all registered subscribers. * Subscribed functions receive the payload directly; subscribed objects receive it via `handleSignal(payload)`. * * This is the primary way to broadcast reactive updates in a signal-based architecture. * * @param detail - Optional data to pass to each subscriber. If omitted, `undefined` is delivered. * * @example * const signal = new Signal<string>() * signal.subscribe(name => console.log("Name changed:", name)) * signal.activate("Alice") * * @example * class Logger { * handleSignal(msg: string) { * console.log("Logged:", msg) * } * } * const logger = new Logger() * signal.subscribe(logger) * signal.activate("Updated") */ activate(detail?: T): void { this.#eventTarget.dispatchEvent({ detail, cancelable: false }) } /** * Subscribes to the signal, so the provided callback will be invoked whenever the signal is activated. * * The subscriber can be either: * - a function that receives the signal's payload, or * - an object implementing `{ handleSignal(detail: T): void }` * * This method supports standard listener options like `{ once: true }` or `{ signal: AbortSignal }` * for controlling the subscription lifecycle. * * @param subscriber - Function or object to be notified on signal activation. * @param options - Optional listener options, such as `once` or `signal` for aborting. * * @example * signal.subscribe(value => console.log("Signal received:", value)) * * @example * signal.subscribe({ handleSignal: value => console.log("Handled:", value) }) * * @example * const controller = new AbortController() * signal.subscribe(fn, { signal: controller.signal }) * controller.abort() // automatically removes the listener */ subscribe(subscriber: Subscriber<T>, options?: ISubscribeOptions): void { super.subscribe(subscriber, options) } /** * Unsubscribes a previously registered subscriber from the signal. * * The given subscriber must be the **same function or object** that was passed to {@link subscribe}. * This ensures the callback will no longer be triggered by future signal activations. * * This method is functionally equivalent to using an `AbortSignal` when subscribing, * but allows for explicit manual unsubscription. * * @param subscriber - The function or object previously passed to `subscribe`. * @param options - (Optional) Listener options matching those used during subscription. * * @example * const callback = (value: number) => console.log(value) * signal.subscribe(callback) * signal.unsubscribe(callback) * * @example * const handler = { handleSignal: (x: string) => { ... } } * signal.subscribe(handler) * signal.unsubscribe(handler) * * @example * // Alternative: use AbortSignal * const controller = new AbortController() * signal.subscribe(fn, { signal: controller.signal }) * controller.abort() */ unsubscribe(subscriber: Subscriber<T>, options?: IUnsubscribeOptions): void { super.unsubscribe(subscriber, options) } }