Yes, that’s right. You won’t need Zone.js if you use signals. I dare say it looks like Angular wants to get rid of Zone.js in the future. But we don’t have to fear anything, because for the moment we will have backward compatibility with Zone.js.
What are Angular Signals 🤔?
Signals are a reactive value, technically are a zero-argument function [(() => T
)] , when we execute it they return the value. We can say that signal is a special type of value that can be observed 🧐 for changes.
The concept
The main concept of signals in Angular is the ability to granularly update parts of the component. We don’t want to render the whole component or its tree, but the exact small part of the component that has been changed. And this is why signals were introduced.
How do we create a signal ?
We create and initialize a signal by calling the function signal()
// Signature of signal function
export function signal<T>(
initialValue: T, equal: ValueEqualityFn<T> = defaultEquals): SettableSignal<T>;
//How use it
const movies = signal<Movie[]>([]);
The signal
function is a TypeScript function that creates a Signal. It takes two arguments:
initialValue
: Represent the initial value of the signal, and it can be of any typeT
equal
: This is an optional function that compares two values of typeT
. The default value forequal
is a function calleddefaultEquals
.ThedefaultEquals
function compares two values of the same typeT
using a combination of the===
operator and theObject.is
method.
The signal
function returns a SettableSignal<T>
.Signal are a getter function, but the type SettableSignal
give as the possibilty to modifiy the value by three methods :
set
[set(value: T): void] for replacement (set the signal to a new value, and notify any dependents)
movies.set([{name: "ADHM", type: "Romantic", ... }])
update
[update(updateFn: (value: T) => T)] for deriving a new value (Update the value of the signal based on its current value, and notify any dependents), The update operation uses the set() operation for performing updates behind the scenes.
movies.update((movie) => {name: "RAW", type: "Thriller"})
mutate
[mutate(mutatorFn: (value: T) => void)] for performing internal mutation of the current value (Update the current value by mutating it in-place, and notify any dependents)
movies.mutate((moviesList) => {
moviesList.push({name: "RAONE", type: "Action", ...})
})
So Signal is a reactive value that can be observed, updated and notifiy any dependents.
But what does it mean to notify any dependents😕 ?
This is the magic 🪄 part of Signal. Now we will see what dependents are for Signals and how to notify them.
Signal is not just a value that can be modified, it is more than that, Signal is a reactive value 🔃 and is a producer that notify consumers(dependents) when it changes.
So dependents in Signal are any code that has registered an interest in the Signal’s value and wishes to be notified whenever the Signal’s value changes. When the Signal’s value is modified, the Signal will notify all of its dependents, allowing them to react to the change in the Signal’s value. This makes the Signal is the core element of a reactive application, as it allows different parts of the application to automatically update in response to changes in data.
So, how we can add dependents (consumer) to Signal ?
We can add consumers by using effect and computed functions.
effect
Sometimes, when a signal has a new value, we may need to add a side effect. To accomplish this, we can use the effect() function.
Effect schedules and runs a side-effectful function inside a reactive context.
Signature of the effect function :
export function effect(effectFn: () => void): Effect
like this
effect(() => {
console.log(movies);
})
The function inside the effect will re-evaluate with any change that occurs in the signals called inside it. Multiple signals can be added to the effect function.
🔍 I will try to explain the work behind the effect function : 🔍
when we declare an effect function, the effectFn passed as an argument will be added to the list of the consumers of any signals used by the effectFn, such as movies in our example. (Signals used by the effectFn will be the producers).
Then, when the signal has a new value by using the set, update, or mutate operators, the effectFn will be re-evaluated with the new signal value(The producer notifies all consumers of the new values).
The effect()
function returns an Effect
, which is a global reactive effect that can be manually scheduled or destroyed. An Effect
has three operations.
schedule(): 🖱️ Schedule the effect for manual execution, if it’s not already.
destroy(): 🧹 Shut down the effect, removing it from any upcoming scheduled executions.
consumer: 👉 Direct access to the effect’s Consumer for advanced use cases.
computed
What if there is another value that depends on the values of other signals, and needs to be recalculated 🔄 whenever any of those dependencies changes?
In this case, we can use a computed()
function to create a new signal that automatically updates whenever its dependencies change.
computed()
creates a memoizing signal, which calculates its value from the values of some number of input signals.
Signature of the computed function :
export function computed<T>(
computation: () => T, equal: ValueEqualityFn<T> = defaultEquals): Signal<T>
We can use it in this manner
numberOfMovies = computed(() => movies.length)
So computed function will return another Signal, any signals used by computation will be tracked as dependencies, and the value of the new signal recalculated whenever any of those dependencies changes.
Note that the
computed
function returns aSignal
and not aSettableSignal
, which means it cannot be manually modified using methods such asset
,update
, ormutate
. Instead, it is updated automatically whenever one of its dependent parents signals changes.
and now we can add and effect or create another signal with computed function based of this new Signal.
Any change in values now will be propagated in the dependencies graph 🌳.
Conclusion
We’re finally getting meaningful changes to the framework. Honestly, I’m looking forward to it, and I’m very excited. 🤩
I think signals will enhance Angular and its performance. Signals will become the primary way we indicate that a value should trigger change detection, so we’ll use them for every value in our application that changes. And they will also just be generally useful for reactive and declarative coding.
But will Angular signals be a game changer? Feel free to comment on this post below.