Saltar al contenido

observable(): exponer un signal como observable

observable toma un accessor de Solid y devuelve un objeto que implementa el protocolo de observables de TC39, con un metodo subscribe y la clave Symbol.observable, de modo que RxJS y cualquier libreria compatible pueden consumir tu signal como si fuera una fuente nativa. Es el dual exacto de from: donde from trae el mundo push hacia dentro, observable expone el mundo pull hacia fuera, y juntos permiten el viaje de ida y vuelta signal, operadores de RxJS, signal otra vez.

Un signal visto desde fuera

observable recibe un accessor y devuelve un objeto con dos capacidades. La primera es un método subscribe(observer), donde observer sigue la forma de TC39 —un objeto con next, error y complete, o directamente una función que hace de next—. Al suscribirte, observable monta por dentro un efecto reactivo aislado en su propio createRoot: lee el accessor, llama a observer.next con el valor actual de inmediato, y vuelve a llamarlo cada vez que el signal cambia. Te devuelve un objeto con unsubscribe que dispone ese efecto.

TSX
import { createSignal, observable } from "solid-js";const [cuenta, setCuenta] = createSignal(0);const cuenta$ = observable(cuenta);const sub = cuenta$.subscribe((v) => console.log("emite:", v)); // emite: 0 yasetCuenta(1); // emite: 1setCuenta(2); // emite: 2sub.unsubscribe();

La segunda capacidad es la clave Symbol.observable: el objeto devuelto se implementa a sí mismo bajo esa clave. Ese es el apretón de manos del protocolo TC39, el que permite que una librería ajena diga “dame tu observable” sin saber nada de Solid.

El protocolo TC39 y la interoperabilidad con RxJS

Symbol.observable es una propuesta de estándar que fija un contrato mínimo para que observables de distintas librerías se entiendan. RxJS lo respeta: su propia función from sabe reconocer cualquier objeto que exponga Symbol.observable y adoptarlo como una fuente nativa. Ahí está la pieza que abre todo el catálogo de operadores de RxJS a un signal de Solid.

TSX
import { createSignal, observable } from "solid-js";import { from as rxFrom } from "rxjs";import { debounceTime, map, distinctUntilChanged } from "rxjs/operators";const [texto, setTexto] = createSignal("");const busqueda$ = rxFrom(observable(texto)).pipe(  map((t) => t.trim()),  distinctUntilChanged(),  debounceTime(300),);busqueda$.subscribe((consulta) => lanzarBusqueda(consulta));

Fíjate en lo que ocurre: texto es un signal de Solid corriente, editado por un input; observable(texto) lo expone; el from de RxJS lo adopta; y a partir de ahí operan map, distinctUntilChanged y debounceTime, que son territorio donde RxJS brilla. Has delegado la coreografía temporal —recortar, deduplicar, antirebotar— a la herramienta que mejor la expresa, sin sacar el estado del grafo de Solid.

Duales: el viaje de ida y vuelta

from y observable son operaciones inversas, y verlas como par es lo que desbloquea el patrón más potente: el viaje redondo. Sacas un signal a RxJS con observable, aplicas los operadores que quieras, y traes el resultado de vuelta a Solid con from. El estado nunca deja de ser reactivo dentro de Solid; solo hace una excursión por RxJS para aprovechar sus operadores temporales.

TSX
import { createSignal, from, observable, createEffect } from "solid-js";import { from as rxFrom } from "rxjs";import { debounceTime } from "rxjs/operators";const [texto, setTexto] = createSignal("");// solid -> rxjs -> operadores -> solidconst textoDebounced = from(  rxFrom(observable(texto)).pipe(debounceTime(300)),);createEffect(() => console.log("estable:", textoDebounced())); // Accessor de nuevo

El resultado, textoDebounced, vuelve a ser un Accessor<string | undefined> que lees en el grafo como cualquier signal. Las dos fronteras —salida con observable, entrada con from— son simétricas, y esa simetría es la que hace que integrar RxJS en Solid no sea un parche sino una composición limpia.

La forma del observer y sus límites

El observer que pasas a subscribe sigue la forma de TC39: un objeto con next, error y complete, o una función suelta que hace de next. observable invoca next en cada cambio del signal, pero conviene conocer sus límites para no llevarse sorpresas al componer con operadores ajenos.

TSX
const sub = observable(cuenta).subscribe({  next: (v) => pintar(v),  error: (e) => console.error(e),     // rara vez se dispara solo  complete: () => console.log("fin"), // nunca por su cuenta});

Dos límites importan. El primero: observable no emite complete por iniciativa propia, porque un signal no “termina” —vive mientras viva su dueño—; la secuencia se corta cuando llamas a unsubscribe o cuando el dueño se dispone. El segundo: si leer el accessor lanza una excepción, esta se propaga como un error normal de JavaScript en vez de enrutarse automáticamente al canal error del observer, así que un catchError de RxJS aguas abajo no lo verá salvo que lo captures tú. Son las costuras esperables de un puente entre dos modelos de error distintos, y saberlas evita depurar a ciegas cuando el flujo cruzado no se comporta como un observable puro.