Why Signals

Why do we need signals in angular? We already have BehaviorSubject which seems to provide the same functionality. let’s understand how Signals are better then BehaviorSubject as a reactive building block in angular.

Well, signals are easier and they still cover a significant chunk of the use cases that RxJS covers. for angular beginners or people that are not interested in RxJS or don’t know how to work with RxJS in a professional way, it’s the perfect alternative tool for a basic reactive building block in angular.

Let’s see why signals are better tool for a reactive building block in angular than BehaviorSubject.

In the example below we can see how to create and access a signals and BehaviorSubject:


// creating a Signal and BehaviorSubject
signalCount = signal(0);     //signals
subjectCount = new BehaviourSubject(0); //behaviourSubject
 
// Accessing them in template
{{ signalCount() }}                    //signals 
{{ subjectCount | async }}          //behaviourSubject
{{ subjectCount.getValue() }}     //behaviourSubject
{{ subjectCount.value }}             //behaviourSubject

// Accessing them in a class  imperatively:
logValue() { console.log(signalCount.count()) }    //signals 
logValue() { console.log(this.subjectCount.value) }   //behaviourSubject



It looks like there are no big differences between the two. The big difference is when we get into “computing derived values”, which means that when a behaviourSubject or a signal is  getting a new value, we want to run a computation on that value to produce some other value. lets see an example:


// creating a Signal and BehaviorSubject
signalCount = signal(0);     //signals
subjectCount = new BehaviourSubject(0); //behaviourSubject
 
// making computation
addOneSignal = computed(() => this.signalCount + 1);
addOneSubject = this.subjectCount.pipe(map(count) => count + 1)

we use this reactive variables to make a new variable as a result of a computation, so now we have a new value in addSignal and addSubject. it seems that there is no big difference between the two but there is.

when we pipe to a BehaviorSubject (like ‘map’ in our case), it stops being a BehaviorSubject and turns to an Observable (‘addOneSubject’ is an observable). it means that we in the world of observables now, we cant access the value directly, we will need to subscribe and unsubscribe the async pipe, the unintended consequences of multiple subscriptions and side effects, hot and cold observables and so on.

this is how we will access the computed values:


// Accessing the computed values with signals
console.log(this.addOneSignal);

// Accessing the computed values with observables
this.addOneSubject.subscribe((value) => console.log(value))

Let’s see another example. this time we to use two reactive values to make a computation and create a new derived value:


// signals example
signal1 = signal(10);
signal2 = signal(20);
 
signalComputationResult = computed(() => this.signal1() + this.signal2());

changeSignals() {
  this.signal1.set(10);
  this.signal2.set(10)
}
/*
signalComputationResult will be 30 and when 
changeSignals run it will 50 (glitch free computation)
*/



// BehaviorSubject example
subject1 = new BehaviourSubject(10);
subject2 = new BehaviourSubject(20);

subjectComputationResult = combineLatest([this.subject1, this.subject2]).pipe(
  map(([one, two]) = one + two)
);

changeSignals() {
  this.subject1.next(10);
  this.subject2.next(10)
}
/*
subjectComputationResult will be 30 and for a sec will be 40 
and only then will be 50 (comineLatest have the diamond problem)
*/


combineLatest suffer from the diamond problem, except that, combineLatest wont emit a value if all the observables inside have at least one value (subject1, and subject2).

let’s see a last example, this time with side effect:


// signals example
myService = inject(MyService);
signalCount = this.myService.getCount();

addOneSignal = computed(() => this.signalCount + 1);

constructor() {
  effect(() => {
    console.log('count changed!', this.addOneSignal)
  });
} 

with signals, the effect will log a message initially and every time that the signal inside effect (in the example is the signalCount) will be change, if we will change the value to 100 twice it wont be trigger the second time, the value actually have to be differnet from the one before to trigger the effect.


// signals example
myService = inject(MyService);

subjectCount = this.myService.getCount().pipe(
  tap((count) => console.log('count changed!', count))
);

 
addOneSubject = this.subjectCount.pipe(map(count) => count + 1)

if we will try to access the subjectCount value a few times in the template using the async pipe, it will trigger the side effect every time we access the value. and if we have derived value like addOneSubject (from our other example) that its value derived from subjectCount, every time we try to access addOneSubject in the template, it will trigger the side effect.

As you can see, it’s much easy to use signals and they can be better and easier for people that don’t know the concepts of RxJS.