You are on page 1of 42

2023. 12. 12.

14:56 Angular Signals: Complete Guide

ANGULAR CORE

Angular Signals: Complete


Guide
A complete guide on how to use Signals in an Angular
application. Learn signals, their benefits, best practices,
and patterns, and avoid the most common pitfalls.

ANGULAR UNIVERSITY
7 DEC 2023

Angular Signals were originally introduced in Angular 17, and they are
really powerful.

The whole goal of signals is to give developers a new easy-to-use


reactive primitive that can be used to build applications in reactive
style.

Signals have an easy-to-understand API for reporting data changes to


the framework, allowing the framework to optimize change detection
and re-rendering in a way that so far was just not possible.

With Signals, Angular will be able to determine exactly what parts of the
page need to be updated and update only those and nothing more.

This comes in contrast with what currently happens with for example
default change detection, where Angular has to check all the
components on the page, even if the data that they consume didn't
change.

https://blog.angular-university.io/angular-signals/ 1/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Signals are all about enabling very fine-grained updates to the DOM
that are just not possible with the current change detection systems
that we have available.

Signals are all about increasing the runtime performance of your


application, by getting rid of Zone.js.

Even without the runtime performance benefits, signals are just a great
way to build applications in reactive style in general.

Signals are also way easier to use and understand than RxJS when it
comes to propagating data changes throughout an application.

Signals are a game changer, and they take reactivity in Angular to a


whole new level!

In this guide, we will explore the Signals API in detail, explain how
signals work in general, as well as talk about the most common pitfalls
that you should watch out for.

Note: Signals are not yet plugged into the Angular change
detection system.At this point, even though most of the Signals
API is already out of developer preview, there aren't yet any signal-
based components.
This could be available around Angular 17.2, to be confirmed.
But that doesn't prevent us from already starting to learn right now
about signals and their advantages, in preparation for what is to
come very soon.

Table of Contents
This post covers the following topics:

What are Signals?

How to read the value of a signal?

How to modify the value of a signal?


https://blog.angular-university.io/angular-signals/ 2/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

The update() Signal API

What is the main advantage of using signals instead of primitive


values?

The computed() Signal API

How do we subscribe to a signal?

Can we read the value of a signal from a computed signal


without creating a dependency?

What is the major pitfall to look out for when creating computed
signals?

Are signal dependencies determined based only on the initial call


to the compute function?

Signals with array and object values

Overriding the signal equality check

Detecting signal changes with the effect() API

What is the relation between Signals and change detection?

How to fix the error NG0600: Writing to signals is not allowed

How to set signals from inside an effect, if needed

The default effect clean-up mechanism

How to clean up effects manually

Performing cleanup operations when an effect is destroyed

Read-only signals

Using a signal in multiple components

How to create signal-based reactive data services

Signals and OnPush components

Can I create signals outside of components/stores/services?

How do Signals compare to RxJs?

Summary

https://blog.angular-university.io/angular-signals/ 3/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Note: If you are looking to learn about other Angular 17 features besides
signals, check out the following posts:

Angular @if: Complete Guide

Angular @for: Complete Guide

Angular @switch: Complete Guide

Angular @defer: Complete Guide

What are Signals?


Simply put, a signal is a reactive primitive that represents a value and
that allows us to change that same value in a controlled way and track
its changes over time.

Signals are not a new concept exclusive to Angular. They have been
around for years in other frameworks, sometimes under different
names.

To better understand signals, let's start with a simple example that does
not use signals yet, and then re-write the same example using the
Signals API.

To start, let's say that we have a simple Angular component with a


counter variable:

@Component(
selector: "app",
template: `

<h1>Current value of the counter {{counter}}</h1>

<button (click)="increment()">Increment</button>

`)
export class AppComponent {
https://blog.angular-university.io/angular-signals/ 4/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

counter: number = 0;

increment() {
this.counter++;
}

As you can see, this is a very simple component.

It just displays a counter value, and it has a button to increment the


counter. This component uses the Angular default change detection
mechanism.

This means that the {{counter}} expression as well as any other


expressions on the page are being checked for changes after each
event, such as a click on the increment button.

As you can imagine, that could be a potentially ineffective way to try to


detect what needs to be updated on the page.

In the case of our component, this approach is actually even necessary,


because we are using a mutable plain Javascript member variable like c

ounter to store our state.

So when an event occurs, just about anything on the page could have
affected that data.

Furthermore, the click on the increment button could have easily


triggered changes anywhere else on the page as well, and not just inside
this component.

Imagine for example that we would call a shared service that would
affect multiple parts of the page.

https://blog.angular-university.io/angular-signals/ 5/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

With default change detection, there is no way for Angular to know


exactly what has changed on the page, so that is why we cannot make
any assumptions about what happened, and we need to check
everything!

Because we have no guarantees of what could or could not have


changed, we need to scan the whole component tree and all the
expressions on every component.

With default change detection, there is no other way.

But here is where signals come to the rescue!

Here is the same simple example, but this time re-written using the
Signals API:

@Component(
selector: "app",
template: `

<h1>Current value of the counter {{counter()}}</h1>

<button (click)="increment()">Increment</button>

`)
export class AppComponent {

counter = signal(0);

constructor() {

console.log(`counter value: ${this.counter()}`)

increment() {

https://blog.angular-university.io/angular-signals/ 6/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

console.log(`Updating counter...`)

this.counter.set(this.counter() + 1);

As you can see, the signal-based version of our component doesn't look
that much different.

The major difference is that we are now wrapping our counter value in a
signal using the signal() API, instead of just using a plain counter

Javascript member variable.

This signal represents the value of a counter, that starts at zero.

We can see that the signal acts like a container for the value that we
want to keep track of.

How to read the value of a signal?


The signal wraps the value that it represents, but we can get that value
at any time just by calling the signal as a function, without passing it any
arguments.

Notice the code on the constructor of the signal-based version of our


AppComponent:

constructor() {

console.log(`counter value: ${this.counter()}`)

https://blog.angular-university.io/angular-signals/ 7/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

As you can see, by calling counter() , we get back the value wrapped in
the signal, which in this case would resolve to zero (the initial value of
the signal).

How to modify the value of a signal?


There are a couple of different ways to change the value of a signal.

In our case, we are using the set() API in our implementation of the
counter increment function:

increment() {

console.log(`Updating counter...`)

this.counter.set(this.counter() + 1);

We can use the set() API to set any value that we need in the signal, as
long as the value is of the same type as the initial value of the signal.

So in the case of our counter signal, we can only set numbers because
the initial value of the signal was zero.

The update signal API


Besides the set() API, we also have available the update() API.

Let's use it to re-write our counter increment function:

increment() {

console.log(`Updating counter...`)

https://blog.angular-university.io/angular-signals/ 8/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

this.counter.update(counter => counter + 1);

As you can see, the update API takes a function that receives the
current value of the signal as an input argument, and then it returns the
new value of the signal.

Both versions of the increment method are equivalent and work equally
well.

What is the main advantage of using signals


instead of primitive values?
At this point, we can now see that a signal is just a lightweight wrapper
or a container for a value.

So what is the advantage of using it then?

The main advantage is that we can get notified when the signal value
changes, and then do something in response to the new signal value.

This is unlike the case when we used just a plain value for our counter.

With a plain value, there is no way for us to get notified when a value
changes.

But with Signals? Easy!

And that's the whole point of using signals in the first place.

The computed() Signal API


Signals can be created and derived from other signals. When a signal
updates, all its dependent signals will then get updated automatically.

https://blog.angular-university.io/angular-signals/ 9/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

For example, let's say that we would like to have a derived counter on
our component, that is the value of the counter multiplied by 10.

We can create a derived signal based on our counter signal, by using the
computed() API:

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

<h3>10x counter: {{derivedCounter()}}</h3>

<button (click)="increment()">Increment</button>

`)
export class AppComponent {

counter = signal(0);

derivedCounter = computed(() => {

return this.counter() * 10;

})

increment() {

console.log(`Updating counter...`)

this.counter.set(this.counter() + 1);

https://blog.angular-university.io/angular-signals/ 10/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

The computed API works by taking in one or more source signals, and
creating a new signal.

When the source signal changes (in our case the counter signal), the
computed signal derivedCounter gets updated as well, instantly.

So when we click on the Increment button, the counter will have the
initial value of 1 and the derivedCounter will be 10, then 2 and 20, 3 and
30, etc.

How do we subscribe to a signal?


Notice that the derivedCounter signal did not subscribe in any explicit
way to the source counter signal.

The only thing that it did was call the source signal using counter()

inside its computed function.

But that's all that we needed to do to link the two signals together!

Now whenever the counter source signal has a new value, the derived
signal also gets updated automatically.

It all sounds kind of magical, so let's break down what is going on:

Whenever we are creating a computed signal, the function


passed to computed() is going to get called at least once, to
determine the initial value of the derived signal

Angular is keeping track as the compute function is being called


and takes note when other signals that it knows are being used.

Angular will notice that when the value of derivedCounter is


being calculated, the signal getter function counter() is called.

So Angular now knows that there is a dependency between the


two signals, so whenever the counter signal gets set with a new
value, the derived derivedCounter will also be updated

https://blog.angular-university.io/angular-signals/ 11/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

As you can see, this way the framework has all the information about
what signals depend on other signals.

Angular knows the whole signal dependency tree and knows how
changing the value of one signal will affect all the other signals of the
application.

Can we read the value of a signal from a


computed signal without creating a
dependency?
There could be certain advanced scenarios where we want to read the
value of a signal from another computed signal, but without creating
any dependency between both signals.

This should be rarely needed, but if you ever run into a situation where
you need to do this, here is how you do it:

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

<h3>10x counter: {{derivedCounter()}}</h3>

`)
export class AppComponent {

counter = signal(0);

derivedCounter = computed(() => {

return untracked(this.counter) * 10;

https://blog.angular-university.io/angular-signals/ 12/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

})

By using the untracked API, we can access the value of the counter
signal without creating a dependency between the counter and the
derivedCounter signal.

Notice that this untracked feature is an advanced feature that should be


rarely needed, if ever.

If you find yourself using this feature very often, something is not right.

What is the major pitfall to look out for when


creating computed signals?
For everything to work smoothly, we need to make sure that we
compute our derived signals in a certain way.

Remember, Angular will only consider that one signal depends on


another if it notices that a signal is needed to calculate the value of
another signal.

This means that we need to be careful with introducing conditional logic


inside the computed function.

Here is an example of how things could easily go wrong:

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

<h3>Derived counter: {{derivedCounter()}}</h3>

https://blog.angular-university.io/angular-signals/ 13/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

<button (click)="increment()">Increment</button>

<button (click)="multiplier = 10">


Set multiplier to 10
</button>

`)
export class AppComponent {

counter = signal(0);

multiplier: number = 0;

derivedCounter = computed(() => {

if (this.multiplier < 10) {


return 0
}
else {
return this.counter() * this.multiplier;
}
})

increment() {

console.log(`Updating counter...`)

this.counter.set(this.counter() + 1);

As you can see in this example, there is some conditional logic in the
compute function.

We are calling the counter() source signal like before, but only if a certain
condition is met.

https://blog.angular-university.io/angular-signals/ 14/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

The goal of this logic would be to set the value of the multiplier
dynamically, via some user action like clicking on the "Set multiplier to
10" button.

But this logic does not work as expected!

If you run it, the counter itself will still be incremented.

But the expression {{derivedCounter()}} will always be evaluated to zero,


both before and after the "Set multiplier to 10" gets clicked.

The problem is that when we calculate the derived value, no calls to cou

nter() are made initially.

The call to counter() is made inside an else branch, that does not get run
initially.

So Angular does not know that there was a dependency between the
counter signal and the derivedCounter signal.

Those are considered by Angular as two completely independent


signals, without any connection.

This is why when we update the value of the counter, derivedCounter


does not get updated.

This means that we need to be somewhat careful when defining


computed signals.

If a derived signal depends on a source signal, we need to make sure we


call the source signal every time that the compute function gets called.

Otherwise, the dependency between the two signals will be broken.

This does not mean that we can't have any conditional branching at all
inside a compute function.

https://blog.angular-university.io/angular-signals/ 15/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

For example, the following code would work correctly:

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

<h3>Derived counter: {{derivedCounter()}}</h3>

<button (click)="increment()">Increment</button>

<button (click)="multiplier = 10">


Set multiplier to 10
</button>

`)
export class AppComponent {

counter = signal(0);

multiplier: number = 0;

derivedCounter = computed(() => {


if (this.counter() == 0) {
return 0
}
else {
return this.counter() * this.multiplier;
}
})

increment() {

console.log(`Updating counter...`)

this.counter.set(this.counter() + 1);

https://blog.angular-university.io/angular-signals/ 16/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

With this version of the code, our compute function is now calling this.co

unter() in every call.

So Angular can now identify the dependency between the two signals,
and the code works as expected.

This means that after clicking the "Set multiplier to 10" button the first
time, the multiplier will be applied correctly.

Are signal dependencies determined based


only on the initial call to the computed
function?
No, instead the dependencies of a derived signal are identified
dynamically based on its last calculated value.

So with every new call of the computed function, the source signals of
the computed signal are identified again.

This means that a signal's dependencies are dynamic, they are not fixed
for the lifetime of the signal.

Again, this means that we need to be careful with any conditional logic
that we create when defining a derived signal.

Signals with array and object values


So far, we have been showing examples of a signal with a primitive type,
like a number.

https://blog.angular-university.io/angular-signals/ 17/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

But what would happen if we define a signal whose value is an array or


an object?

Arrays and objects work mostly the same way as primitive types, but
there are just a couple of things to watch out for.

For example, let's take the case of a signal whose value is an array, and
another signal whose value is an object:

@Component(
selector: "app",
template: `

<h3>List value: {{list()}}</h3>

<h3>Object title: {{object().title}}</h3>

`)
export class AppComponent {

list = signal([
"Hello",
"World"
]);

object = signal({
id: 1,
title: "Angular For Beginners"
});

constructor() {

this.list().push("Again");

this.object().title = "overwriting title";


}

https://blog.angular-university.io/angular-signals/ 18/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

There is nothing special about these signals, in the sense that we can
access their values as usual just by calling the signal as a function.

But the key thing to notice is that unlike a primitive value, nothing
prevents us from mutating the content of the array directly by calling
push on it, or from mutating an object property.

So in this example, the output generated to the screen would be:

"Hello", "World", "Again" in the case of the list

"overwriting title" in the case of the object title

This is of course not how Signals are meant to be used!

Instead, we want to update the value of a signal by always using the set

() and update() APIs.

By doing so, we give a change to all the derived signals for updating
themselves and have those changes reflected on the page.

By directly accessing the signal value and mutating its value directly, we
are bypassing the whole signal system and potentially causing all sorts
of bugs.

It is worth keeping this in mind, we should avoid mutating signal values


directly, and instead always use the Signals API.

This is worth mentioning because there is currently no protective


mechanism in the Signals API for preventing this misuse, like
preventively freezing the array or the object value.

Overriding the signal equality check


https://blog.angular-university.io/angular-signals/ 19/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Another thing worth mentioning regarding array or object signals is that


the default equality check is "===".

This equality check is important because a signal will only emit a new
value if the new value that we are trying to emit is different then the
previous value.

If the value that we are trying to emit is considered to be the same as


the previous value, then Angular will not emit the new signal value.

This is a performance optimization that potentially prevents


unnecessary re-rendering of the page, in case we are systematically
emitting the same value.

The default behavior however is based on "===" referential equality,


which does not allow us to identify arrays or objects that are
functionally identical.

If we want to do that, we need to override the signal equality function


and provide our implementation.

To understand this, let's start with a simple example where we are still
using the default equality check for a signal object.

Then we create a derived signal from it:

@Component(
selector: "app",
template: `

<h3>Object title: {{title()}}</h3>

<button (click)="updateObject()">Update</button>

`)
export class AppComponent {

https://blog.angular-university.io/angular-signals/ 20/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

object = signal({
id: 1,
title: "Angular For Beginners"
});

title = computed(() => {

console.log(`Calling computed() function...`)

const course = this.object();

return course.title;

})

updateObject() {

// We are setting the signal with the exact same


// object to see if the derived title signal will
// be recalculated or not
this.object.set({
id: 1,
title: "Angular For Beginners"
});

In this example, if we click on the Update button multiple times, we are


going to get multiple logging lines in the console:

Calling computed() function...


Calling computed() function...
Calling computed() function...

https://blog.angular-university.io/angular-signals/ 21/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Calling computed() function...


etc.

This is because the default "===" cannot detect that we are passing to
the object signal a value that is functionally equivalent to the current
value.

Because of this, the signal will consider that the two values are different,
and so any computed signals that depend on the object signal will also
get computed.

If we want to avoid this, we need to pass our equality function to the


signal:

object = signal(
{
id: 1,
title: "Angular For Beginners",
},
{
equal: (a, b) => {
return a.id === b.id && a.title == b.title;
},
}
);

With this equality function in place, we are now doing a deep


comparison of the object based on the values of its properties.

With this new equality function, the derived signal is only computed
once no matter how many times we click on the Update button:

Calling computed() function...

https://blog.angular-university.io/angular-signals/ 22/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

It's worth mentioning that in most cases, we shouldn't provide this type
of equality functions for our signals.

In practice, the default equality check works just fine, and using a
custom equality check should rarely make any noticeable difference.

Writing this type of custom equality check could lead to maintainability


issues and all sorts of weird bugs if for example we add a property to
the object and forget to update the comparison function.

Equality functions are covered in this guide just for completeness in the
rare case when you need them, but in general, for most use cases there
is no need to use this feature.

Detecting signal changes with the effect()


API
The use of the computed API shows us that one of the most interesting
properties of signals is that we can somehow detect when they change.

After all, that is exactly what the computed() API is doing, right?

It detects that a source signal has changed, and in response, it


calculates the value of a derived signal.

But what if instead of calculating the new value of a dependent signal,


we just want to detect that a value has changed for some other reason?

Imagine that you are in a situation where you need to detect that the
value of a signal (or a set of signals) has changed to perform some sort
of side effect, that does not modify other signals.

This could be for example:

logging the value of a number of signals using a logging library

https://blog.angular-university.io/angular-signals/ 23/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

exporting the value of a signal to localStorage or a cookie

saving the value of a signal transparently to the database in the


background

etc.

All of these scenarios can be implemented using the effect() API:

//The effect will be re-run whenever any


// of the signals that it uses changes value.
effect(() => {

// We just have to use the source signals


// somewhere inside this effect
const currentCount = this.counter();

const derivedCounter = this.derivedCounter();

console.log(`current values: ${currentCount}


${derivedCounter}`);

});

This effect will print out a logging statement to the console anytime that
the counter or derivedCounter signals emit a new value.

Notice that this effect function will run at least once when the effect is
declared.

This initial run allows us to determine the initial dependencies of the


effect.

Just like in the case of the computed() API, the signal dependencies of
an effect are determined dynamically based on the last call to the effect

https://blog.angular-university.io/angular-signals/ 24/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

function.

What is the relation between Signals and


change detection?
I think you can see where this is going...

Signals allow us to easily track changes to application data.

Now imagine the following: let's say we put all our application data
inside signals!

The first point to mention is that the code of the application wouldn't
become much more complicated because of that.

The Signals API and the signal concept are pretty straightforward, so a
codebase that uses signals everywhere would remain quite readable.

Still, that application code wouldn't be as simple to read as if it was just


using plain Javascript member variables for the application data.

So what is the advantage then?

Why would we want to switch to a signal-based approach for handling


our data?

The advantage is that with signals, we can easily detect when any part
of the application data changes, and update any dependencies
automatically.

Now imagine that the Angular change detection is plugged into the
signals of your application, and that Angular knows in which
components and expressions every signal is being used.

This would enable Angular to know exactly what data has changed in
the application, and what components and expressions need to be

https://blog.angular-university.io/angular-signals/ 25/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

updated in response to a new signal value.

There would no longer be the need to check the whole component tree,
like in the case of default change detection!

If we give Angular the guarantee that all the application data goes inside
a signal, Angular all of a sudden has all the necessary information
needed for doing the most optimal change detection and re-rendering
possible.

Angular would know how to update the page with the latest data in the
most optimal way possible.

And that is the main performance benefit of using signals!

Just by wrapping our data in a signal, we enable Angular to give us the


most optimal performance possible in terms of DOM updates.

And with this, you now have a pretty good view of how signals work and
why they are useful.

The question is now, how to use them properly?

Before talking about some common patterns on how to use signals in


an application, let's first quickly finish our coverage of the effect() API.

How to fix the error NG0600: Writing to


signals is not allowed in a computed or an
effect by default.
By default, Angular does not allow a signal value to be changed from
inside an effect function.

So for example, we can't do this:

https://blog.angular-university.io/angular-signals/ 26/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

@Component({...})
export class CounterComponent {
counter = signal(0);

constructor() {
effect(() => {
this.counter.set(1);
});
}
}

This code is not allowed by default, and for very good reasons.

In the particular case of this example, this would even create an infinite
loop and break the whole application!

How to set signals from inside an effect, if


needed
There are certain cases however where we still want to be able to
update other signals from inside an effect.

To allow that we can pass the option allowSignalWrites to effect :

@Component({...})
export class CounterComponent {
count = signal(0);

constructor() {

effect(() => {
this.count.set(1);
},
{
allowSignalWrites: true

https://blog.angular-university.io/angular-signals/ 27/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

});

}
}

This option needs to be used very carefully though, and only in special
situations.

In most cases, you shouldn't need this option. If you find yourself using
this option systematically, revisit the design of your application because
something is probably wrong.

The default effect clean-up mechanism


An effect is a function that gets called in response to a change in a
signal's value.

And like any function, it can create references to other variables in the
application, via a closure.

This means that just like any function, we need to watch out for the
potential of creating accidental memory leaks when using effects.

To help cope with this, Angular will by default automatically clean up the
effect function, depending on the context where it was used.

So for example, if you create the effect inside a component, Angular will
clean up the effect when the component gets destroyed, and the same
goes for directives, etc.

How to clean up effects manually


But sometimes, for some reason, you might want instead to clean up
your effects manually.

This should only be necessary on rare occasions though.

https://blog.angular-university.io/angular-signals/ 28/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

If you are systematically cleaning up the effects manually everywhere in


your application, something is likely not right.

But in case you need to, an effect() can be manually destroyed by calling
destroy on the EffectRef instance that it returns when first created.

In those cases, you probably also want to disable the default cleanup
behavior by using the manualCleanup option:

@Component({...})
export class CounterComponent {

count = signal(0);

constructor() {

const effectRef = effect(() => {

console.log(`current value: ${this.count()}`);


},
{
manualCleanup: true
});

// we can manually destroy the effect


// at any time
effectRef.destroy();
}
}

The manualCleanup flag disables the default cleanup mechanism,


allowing us to have full control over when the effect gets destroyed.

The effectRef.destroy() method will destroy the effect, removing it from


any upcoming scheduled executions, and cleaning up any references to

https://blog.angular-university.io/angular-signals/ 29/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

variables outside the scope of the effect function, potentially preventing


memory leaks.

Performing cleanup operations when an


effect is destroyed
Sometimes just removing the effect function from memory is not
enough for a proper cleanup.

On some occasions, we might want to perform some sort of cleanup


operation like closing a network connection or otherwise releasing
some resources when an effect gets destroyed.

To support these use cases, we can pass to an effect a onCleanup

callback function:

@Component({...})
export class CounterComponent {

count = signal(0);

constructor() {

effect((onCleanup) => {

console.log(`current value: ${this.count()}`);

onCleanup(() => {
console.log("Perform cleanup action here");
});
});
}
}

This function will be called when the cleanup takes place.

https://blog.angular-university.io/angular-signals/ 30/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Inside the onCleanup function, we can do any cleanup we want, such for
example:

unsubscribing from an observable

closing a network or database connection

clearing setTimeout or setInterval

etc.

Let's now explore a few more signal-related concepts, and then show
some common patterns that you are likely to need when using signals in
your application.

Read-only signals
We have already been using read-only signals already, even without
noticing.

These are signals whose value can't be mutated. These are like the
equivalent of const in the JavaScript language.

Readonly signals can be accessed to read their value but can't be


changed using the set or update methods. Read-only signals do
not have any built-in mechanism that would prevent deep
mutation of their value. - Angular repo

Read-only signals can be created from:

computed()

signal.asReadonly()

Let's try to change the value of a derived signal, just to see what
happens:

https://blog.angular-university.io/angular-signals/ 31/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

<h3>Derived counter: {{derivedCounter()}}</h3>

`)
export class AppComponent {

counter = signal(0);

derivedCounter = computed(() => this.counter() * 10)

constructor() {

// this works as expected


this.counter.set(5);

// this throws a compilation error


this.derivedCounter.set(50);

As we can see, we can set new values for the counter signal, which is a
normal writeable signal.

But we can't set values on the derivedCounter signal, as both the set()
and
update() APIs are unavailable.

This means that derivedCounter is a read-only signal.

https://blog.angular-university.io/angular-signals/ 32/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

If you need to, you can easily derive a read-only signal from a writeable
signal:

@Component(
selector: "app",
template: `

<h3>Counter value {{counter()}}</h3>

`)
export class AppComponent {

counter = signal(0);

constructor() {

const readOnlyCounter = this.counter.asReadonly(

// this throws a compilation error


readOnlyCounter.set(5);

Notice that the other way around is not possible: you don't have an API
to create a writeable signal from a read-only signal.

To do that, you would have to create a new signal with the current value
of the read-only signal.

Using a signal in multiple components


Let's now talk about some common patterns that you have available for
using signals in your application.
https://blog.angular-university.io/angular-signals/ 33/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

If a signal is only used inside a component, then the best solution is to


turn it into a member variable as we have been doing so far.

But what if the signal data is needed in several different places of the
application?

Well, nothing prevents us from creating a signal and using it in multiple


components.

When the signal changes, all the components that use the signal will be
updated.

// main.ts
import { signal } from "@angular/core";

export const count = signal(0);

As you can see, our signal is in a separate file, so we can import it in any
component that needs it.

Let's create two components that use the count signal.

// app.component.ts
import { Component } from "@angular/core";
import { count } from "./main";

@Component({
selector: "app",
template: `
<div>
<p>Counter: {{ count() }}</p>
<button (click)="increment()">Increment from Hundr
</div>
`,
})
https://blog.angular-university.io/angular-signals/ 34/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

export class HundredIncrComponent {


count = count;

increment() {
this.count.update((value) => value + 100);
}
}

Here, we imported the count signal and used it in this component, and
we could do the same with any other component in the application.

In some simple scenarios, this might very well be all that you need.

However, I think that for most applications, this approach will not be
sufficient.

This is a bit like using a global mutable variable in Javascript.

Anyone can mutate it, or in the case of signals, emit a new value by
calling
set() on it.

I think that in most cases, this is not a good idea, for the same reasons
that a global mutable variable is usually not a good idea.

Instead of giving unrestricted access to anyone to this signal, we want


to make sure that the access to this signal is somehow encapsulated
and kept under control.

How to create signal-based reactive data


services
The simplest pattern to share a writeable signal across multiple
components is to wrap the signal in a data service, like so:

https://blog.angular-university.io/angular-signals/ 35/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

@Injectable({
providedIn: "root",
})
export class CounterService {

// this is the private writeable signal


private counterSignal = signal(0);

// this is the public read-only signal


readonly counter = this.counterSignal.asReadonly();

constructor() {
// inject any dependencies you need here
}

// anyone needing to modify the signal


// needs to do so in a controlled way
incrementCounter() {
this.counterSignal.update((val) => val + 1);
}
}

This pattern is very similar to the use of Observable Data Services with
RxJs and a BehaviorSubject (see this guide on it), if you are familiar with
that pattern.

The difference is that this service is much more straightforward to


understand, and there are less advanced concepts at play here.

We can see that the writeable signal counterSignal is kept private from
the service.

Anyone needing the value of the signal can get it via its public read-only
counterpart, the counter member variable.

And anyone needing to modify the value of the counter can only do so in
a controlled way, via the incrementCounter public method.
https://blog.angular-university.io/angular-signals/ 36/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

This way, any validations or error-handling business logic can be added


to the method, and no one can bypass them.

Imagine that there is a rule that says that the counter cannot go above
100.

With this pattern, we can easily implement that on the incrementCounter


method in one single place, rather than repeating that logic everywhere
in the application.

We can also refactor and maintain the application better.

If we want to find out all parts of the application that are incrementing
the counter, we just have to find the usage of the incrementCounter
method using our IDE.

This type of code analysis would not be possible if we would just give
direct access to the signal.

Also, if the signal needs access to any dependencies to work properly,


those can be received in the constructor, like in any other service.

One of the principles at play here that makes this solution more
maintainable is the principle of encapsulation.

We don't want just any part of the application to be able to emit freely
new values for the signal, we want to allow that only in a controlled way.

So in general, if you have a signal shared across multiple components,


for most cases you are probably better off using this pattern, rather than
giving access to the writeable signal directly.

Signals and OnPush components


OnPush components are components that are only updated when their
input properties change, or when Observables subscribed with the asyn

https://blog.angular-university.io/angular-signals/ 37/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

c pipe emit new values.

They are not updated when their input properties are mutated.

Now OnPush components are also integrated with signals.

When signals are used on a component, Angular marks that component


as a dependency of the signal. When the signal changes, the
component is re-rendered.

In the case of OnPush components, they will also be re-rendered when a


signal attached to it is updated:

@Component({
selector: "counter",
template: `
<h1>Counter</h1>
<p>Count: {{ count() }}</p>
<button (click)="increment()">Increment</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CounterComponent {
count = signal(0);

increment() {
this.count.update((value) => value + 1);
}
}

In this example, if we click on the Increment button, the component will


be re-rendered, meaning that Signals are integrated directly with OnPus

h .

This means that we no longer need to inject ChangeDetectorRef and


invoke

https://blog.angular-university.io/angular-signals/ 38/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

markForCheck , to update an OnPush component in this scenario.

Consider the below example that does not use signals:

@Component({
selector: "app",
standalone: true,
template: ` Number: {{ num }} `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ExampleComponent {
num = 1;

private cdr = inject(ChangeDetectorRef);

ngOnInit() {
setInterval(() => {
this.num = this.num + 1;
this.cdr.markForCheck();
}, 1000);
}
}

As you can see, this is all a lot more complicated for the same
equivalent functionality. The signal-based version is much simpler.

Can I create signals outside of


components/stores/services?
Absolutely! You can create signals anywhere you want. No constraint
says that a signal needs to be inside a component, store, or service.

We demonstrated that earlier. That is the beauty and power of Signals.


In most cases though, you probably want to wrap the signal in a service,
as we have seen.

https://blog.angular-university.io/angular-signals/ 39/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

How do Signals compare to RxJs?


Signals are not a direct replacement for RxJs, but they provide an
easier-to-use alternative in certain situations where we would
commonly need RxJs.

For example, signals are a great alternative to RxJS Behavior Subjects


when it comes to transparently propagating data changes to multiple
parts of the application.

I hope that you enjoyed this post, to get notified when new similar posts
like this are out, I invite you to subscribe to our newsletter:

Angular University
Watch 25% of all Angular Video Courses, get timely Angular News and PDFs:

Email*

Subscribe & Get Free Course

You will also get up-to-date news on the Angular ecosystem.

And if you want a deep dive into all the features of Angular Core like sign

als and others, have a look at the Angular Core Deep Dive Course:

https://blog.angular-university.io/angular-signals/ 40/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

Summary
In this guide, we explored the Angular Signals API and the concept of a
new reactive primitive: the signal.

We have learned that by using signals to track all our application states,
we are enabling Angular to easily know what parts of the view need to
be updated.

In summary, the main goals of Signals are:

providing a new reactive primitive that is easier to understand, to


enable us to build applications in a reactive style much more
easily.

avoiding unnecessary re-rendering of components that don't


need to be re-rendered.

avoiding unnecessary checks of components whose data didn't


change.

The signals API is straightforward to use, but beware of some common


pitfalls that you might run into:

https://blog.angular-university.io/angular-signals/ 41/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide

when defining effects or computed signals, be careful when


reading the value of source signals inside conditional blocks

when using array or object signals, avoid mutating the signal


value directly

don't overuse advanced features like providing your equality


functions or doing manual effect cleanup unless it's necessary.
The default behaviors should work just fine in the majority of
cases.

I invite you to try out Signals in your application, and see the magic for
yourself!

https://blog.angular-university.io/angular-signals/ 42/42

You might also like