JavaScript Signal Concept and A Simple Implementation From Scratch.

🚦 Signal in JavaScript
If you are working in the Javascript/Typescript world, You should already heard or know about signal. Every modern frameworks in these days are working with signals except react. Even the angular team has adapted signal as their change detection mechanism and moving toward a zoneless change detection where they used to do monkey patching for the change detection. All the other new frameworks are built around the signal concept.
The Big Questions
So what is the concept of signal? Why it is so special? Is it a new concept in the programming world?
🧠The Core Concept
Signal is a way of reactive programming. Actually signal is nothing new but an implementation of the observer pattern. If you know about the observer design pattern, In this pattern we have a observable and many observers. The observers subscribe to the observable and whenever something changes in the observables in lets all its observers know about the change.
In the concept of signal there is two main building blocks. One is signal and another is effect. We call the observable as signal and the observers as effects.
But here is some tweaks. As observer pattern we do not need to subscribe manually in signal. Rather it will automatically know about the effects that are depending on it. To achieve this we will return getter and setter functions instead of plain values from a signal so whenever the getter is called we can know about the current effect it is running in.
💻 Implementation
So as now we know about the concept of signal lets see its implementation:
let currentEffect = null;
const signal = (value) => {
const observers = [];
let _value = value;
return {
get() {
if (currentEffect) {
observers.push(currentEffect);
}
return _value;
},
set(newValue) {
_value = newValue;
observers.forEach((observer) => observer());
},
};
};
const effect = (callback) => {
currentEffect = callback;
// callback is stored globally to let the getters used in the callback
// about the effects they need to run whenever changed.
callback();
currentEffect = null;
};
Using these two building block we can create another one that is called a computed value and is used in most of the frameworks.
const computed = (callback) => {
const computedSignal = signal(callback());
effect(() => {
computedSignal.set(callback());
// whenever any signal used in the callback will change, this effect
// will run again and the computed value will be updated.
});
return { get: computedSignal.get };
};
🚀 How to use
const a = signal(1);
const b = computed(() => {
return a.get() * 2;
});
effect(() => {
console.log(a.get(), b.get());
});
a.set(a.get() * 2);
Here once a is set to a new value, b will be updated automatically and the effect will also run again. Hence we have achieved reactive programming through this.
So, this was a simple explanation as well as implementation of signals. Obviously modern frameworks takes care of lot of other cases. But for our basic understanding this implementation is enough.
Happy coding!