DEV Community

Dario Mannu
Dario Mannu

Posted on • Edited on

Signals & Hooks vs Observables

In recent years, a particular pattern has emerged to manage reactive user interfaces in JavaScript.
Implementations go under various names, like Signals or some flavours of Hooks, but both look essentially the same.

  const [value, setValue] = createSignal(0);

  document.body.innerHTML = `
    The value is: <span>${value}</span>
  `;

  setTimeout(() => setValue(2), 1000);
Enter fullscreen mode Exit fullscreen mode

So, what do you expect to happen after 1s?

If you guessed nothing, that's right.
The above construct, in fact, doesn't implement any more or less formal reactive interface. It's plain-old static JavaScript like it's the 1990's.

What some frameworks or libraries do is re-run the whole function, re-render the result and check if anything has changed, then they try to find a way to replace the initial result with the new one in the DOM.

This is the Virtual DOM approach, and as you may realise both as a beginner or expert web developer, it is just as overcomplicated behind the scenes as it sounds, let alone the massive performance hit, the testability constraints and the amount of code that runs which are all important concerns.

Now, let's look at another example:

  const value = new BehaviorSubject(0);

  document.body.innerHTML = rml`
    The value is: <span>${value}</span>
  `;

  setTimeout(() => value.next(2), 1000);
Enter fullscreen mode Exit fullscreen mode

This is the approach taken by Rimmel.js a FRP templating library which instead of signals or hooks, encourages us to use Observable streams.
The reason? Observables are reactive by nature and need no dark framework magic to work, so it's light, fast, and you write less code.

If you are new to Observables, check out this no-BS intro to Observables for people who know Promises.

Rimmel natively supports Observables with their variants incl. Subjects and BehaviorSubjects, so if you put them in a template, subscriptions to them will be made behind the scenes and the DOM will be updated directly as soon as you do a:

state.next(2);
Enter fullscreen mode Exit fullscreen mode

This is technically the same as if you did:

  state.subscribe(newValue =>
    element.innerHTML = newValue
  );
Enter fullscreen mode Exit fullscreen mode

Since there is no Virtual DOM, the whole component doesn't ever need to be re-run or re-rendered, no complicated diff algorithms need to perform any guess work to detect and implement changes, no more brainwash about the VDOM being the way to go, etc.

Observable Sources

This is just where it all begins. With Rimmel you can get observable streams straight from DOM events in your templates, in a way that couldn't be easier:

  const eventStream = new Subject()

  document.body.innerHTML = rml`
    <div onmouseover="${eventStream}"> hello </div>
  `;
Enter fullscreen mode Exit fullscreen mode

If you do this, Rimmel will automatically subscribe mouseover events into your observable stream. The rest is up you you. You can pipe it, transform it, fork it, then sink it back to the DOM.

With the immense stream-manipulation power of libraries such as RxJS, by using an observable-aware templating libary, you can create particularly concise, robust and easily testable components for your web apps.

For some playgrounds to play and get a taster of the above, check out Rimmel on Codepen


Which approach do you prefer? Imperative or Functional? Signals or Observables?

Top comments (0)