Professional Documents
Culture Documents
ANGULAR CORE
ANGULAR UNIVERSITY
7 DEC 2023
Angular Signals were originally introduced in Angular 17, and they are
really powerful.
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.
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.
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 is the major pitfall to look out for when creating computed
signals?
Read-only signals
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:
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.
@Component(
selector: "app",
template: `
<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++;
}
So when an event occurs, just about anything on the page could have
affected that data.
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
Here is the same simple example, but this time re-written using the
Signals API:
@Component(
selector: "app",
template: `
<button (click)="increment()">Increment</button>
`)
export class AppComponent {
counter = signal(0);
constructor() {
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
We can see that the signal acts like a container for the value that we
want to keep track of.
constructor() {
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).
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.
increment() {
console.log(`Updating counter...`)
https://blog.angular-university.io/angular-signals/ 8/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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.
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.
And that's the whole point of using signals in the first place.
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: `
<button (click)="increment()">Increment</button>
`)
export class AppComponent {
counter = signal(0);
})
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.
The only thing that it did was call the source signal using counter()
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:
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.
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: `
`)
export class AppComponent {
counter = signal(0);
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.
If you find yourself using this feature very often, something is not right.
@Component(
selector: "app",
template: `
https://blog.angular-university.io/angular-signals/ 13/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
<button (click)="increment()">Increment</button>
`)
export class AppComponent {
counter = signal(0);
multiplier: number = 0;
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.
The problem is that when we calculate the derived value, no calls to cou
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.
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
@Component(
selector: "app",
template: `
<button (click)="increment()">Increment</button>
`)
export class AppComponent {
counter = signal(0);
multiplier: number = 0;
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
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.
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.
https://blog.angular-university.io/angular-signals/ 17/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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: `
`)
export class AppComponent {
list = signal([
"Hello",
"World"
]);
object = signal({
id: 1,
title: "Angular For Beginners"
});
constructor() {
this.list().push("Again");
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.
Instead, we want to update the value of a signal by always using the set
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.
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.
To understand this, let's start with a simple example where we are still
using the default equality check for a signal object.
@Component(
selector: "app",
template: `
<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"
});
return course.title;
})
updateObject() {
https://blog.angular-university.io/angular-signals/ 21/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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.
object = signal(
{
id: 1,
title: "Angular For Beginners",
},
{
equal: (a, b) => {
return a.id === b.id && a.title == b.title;
},
}
);
With this new equality function, the derived signal is only computed
once no matter how many times we click on the Update button:
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.
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.
After all, that is exactly what the computed() API is doing, right?
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.
https://blog.angular-university.io/angular-signals/ 23/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
etc.
});
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.
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.
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.
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
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 with this, you now have a pretty good view of how signals work and
why they are useful.
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!
@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.
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.
https://blog.angular-university.io/angular-signals/ 28/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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() {
https://blog.angular-university.io/angular-signals/ 29/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
callback function:
@Component({...})
export class CounterComponent {
count = signal(0);
constructor() {
effect((onCleanup) => {
onCleanup(() => {
console.log("Perform cleanup action here");
});
});
}
}
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:
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.
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: `
`)
export class AppComponent {
counter = signal(0);
constructor() {
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.
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: `
`)
export class AppComponent {
counter = signal(0);
constructor() {
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.
But what if the signal data is needed in several different places of the
application?
When the signal changes, all the components that use the signal will be
updated.
// main.ts
import { signal } from "@angular/core";
As you can see, our signal is in a separate file, so we can import it in any
component that needs it.
// 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
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.
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.
https://blog.angular-university.io/angular-signals/ 35/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
@Injectable({
providedIn: "root",
})
export class CounterService {
constructor() {
// inject any dependencies you need here
}
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.
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
Imagine that there is a rule that says that the counter cannot go above
100.
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.
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.
https://blog.angular-university.io/angular-signals/ 37/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
They are not updated when their input properties are mutated.
@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);
}
}
h .
https://blog.angular-university.io/angular-signals/ 38/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
@Component({
selector: "app",
standalone: true,
template: ` Number: {{ num }} `,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class ExampleComponent {
num = 1;
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.
https://blog.angular-university.io/angular-signals/ 39/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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*
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.
https://blog.angular-university.io/angular-signals/ 41/42
2023. 12. 12. 14:56 Angular Signals: Complete Guide
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