Published 10-09-2023
Signals are an amazing addition to Angular. - What makes them so great?
Simplicity.
The majority of the signals interface can be expressed in one small snippet
import { signal, computed, effect } from '@angular/core';
export class SignalExample {
// Init
count = signal(1);
// Get (Same in Template || Typescript)
getCount = () => this.count();
// Setters
reset = () => this.count.set(1);
increment = () => this.count.update((c) => c + 1);
// Computed
doubled = computed(() => this.count() * 2);
// Effects
logCount = effect(() => console.log(this.doubled()));
}
// Omitting untracked & mutate
We can also take a simple example of state, such as count state, and express it in very few lines / concepts compared to its rxjs alternative. i.e. Signals simplify the concepts / lines of code needed to write reactive Angular apps.
export class SignalCount {
count = signal(1);
increment_count() { this.count.update(c => c + 1) }
log_count = effect(() => console.log(this.count()))
}
export class RxjsCount {
count = BehaviorSubject(1);
count$ = count.pipe(
scan((acc, curr) => acc + curr),
tap((count) => console.log(count)),
takeUntilDestroyed()
)
increment_count() { this.count.next(1) }
count$.subscribe();
}
Let’s break it down.
To create a signal, simply pass a value to signal
. This can be anything. Primitive, object, etc…
and to get its value, call it like a function.
person = signal({ name: 'erik' });
person(); // → { name: 'erik' }
// Template
{{person()}}
Compare to an Observable, to get the value from an observable we have to:
async
pipe if in a templatesubscribe
if in typescript (and remember to take/unsubscribe)A signal works the same whether we’re in typescript or a template. And we don’t have to handle unsubscribing.
person()
{{person()}}
There are 2 ways to update a signal. (Which will reactively update anywhere we get the signals value)
set
update
mutate
⚠️ mutate has been dropped from Angular Signals. IMO mutate was a step in the wrong direction and I think this is a great changeWe can really do most operations with just set
. - update
can be viewed as a helpful convenience function.
increment = () => this.count.update((c) => c + 1);
// Or
increment_with_set = () => this.count.set(this.count() + 1)
// Template
<button (click)="increment()">+</button>
Computed lets us derive a new read-only signal from another signal(s). Computed introduces a new concept known as dependencies.
Dependencies are actually quite simple. Any signal referenced inside of the computed function is a dependency of that computed signal.
A few simple examples below with different data types. Again, any signal referenced in the computed function is tracked as a dependency. When that signal changes, the computed function will re-evaluate.
doubled = computed(() => this.count() * 2)
person_name = computed(() => this.person().name)
first_item = computed(() => this.items()[0])
completed = computed(() => this.items().filter(v => v.completed))
Important - dependencies include signals nested inside of functions.
doubled = computed(() => get_count() * 2)
function get_count() {
return this.count(); // → `doubled` tracks `count`
}
Effects are very similar to computed in the sense that they track dependencies, i.e. track signals referenced in their callback. Effects serve a different purpose than computed. Their purpose is to invoke side effects.
Like computed, effects are re-evaluated when their dependencies change. In the example below, whenever count
is updated, logCount
logs the new value.
count = signal(1)
logCount = effect(() => console.log(this.count()));
If you’d like to learn about effects in greater details see Angular Signals: Effective Effects
An important part of an effect
and computed
is untracked
. Untracked allows you to use a signal inside of an effect/computed without adding that signal as a dependency. i.e. changes to signals inside of untracked
will not cause the effect/computed to re-evaluate.
a = signal(1)
b = signal(1)
c = effect(() => {
const a = this.a();
untracked(() => console.log(a + this.b()))
});
// → Only logs when `a` changes
That’s it. Looking for more on Signals? Read the rest of the series here.
Enjoyed this article? Content similar to this is available on Flotes as studyable Notebooks. Information is delivered in an Anki-style flashcard way, that allows you to fill in blanks and evaluate difficultly, to maximize learning efficiency.