In the last lesson a plain property held the "liked" state. That is fine for small demos, but a plain property gives Angular no way to know when it changed. Signals fix that. A signal is a container for a value that Angular can watch, so anything that uses the signal updates itself automatically.
What you will learn
- How to create a signal and read its value
- How to change a signal with
set()andupdate() - Why signals make updates dependable
Create and read a signal
You create a signal with the signal() function and an initial value. You read it by calling it like a function:
import { signal } from '@angular/core';
const count = signal(0);
console.log(count()); // 0
The parentheses matter. count is the signal, and count() is its current value.
Change a signal
There are two ways to change one:
set(value)replaces the value.update(fn)calculates the new value from the old one.
count.set(10);
count.update((value) => value + 1); // now 11
A counter component
// src/app/counter/counter.ts
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<h2>Counter: {{ count() }}</h2>
<button type="button" (click)="increase()">Increase</button>
<button type="button" (click)="decrease()">Decrease</button>
<button type="button" (click)="reset()">Reset</button>
`,
})
export class Counter {
protected readonly count = signal(0);
protected increase(): void {
this.count.update((value) => value + 1);
}
protected decrease(): void {
this.count.update((value) => value - 1);
}
protected reset(): void {
this.count.set(0);
}
}
// src/app/app.ts
import { Component } from '@angular/core';
import { Counter } from './counter/counter';
@Component({
imports: [Counter],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {}
<!-- src/app/app.html -->
<main>
<h1>Signals</h1>
<app-counter />
</main>
When the template reads count(), Angular remembers that this part of the page depends on that signal. When the signal changes, only the parts that read it are updated. You never tell Angular to refresh anything.
Notice that the property is readonly. The signal object itself never changes, only the value inside it does.
Signals with other types
The type is worked out from the first value. When it needs to hold more than that value, give the type in angle brackets:
const name = signal<string | null>(null);
const tags = signal<string[]>([]);
Update arrays and objects without mutating them
Angular sees a change when you give the signal a new value. If you change an array in place with push, the signal does not notice. Create a new array instead:
tags.update((list) => [...list, 'angular']); // good
// tags().push('angular'); // bad: the signal is not told
The [...list, 'angular'] syntax copies the old items and adds one more. The same idea works for objects: user.update((u) => ({ ...u, name: 'Sam' })).
Why use signals instead of plain properties?
Modern Angular projects are created without the older change-detection library, zone.js. In such apps, Angular refreshes the page when it is told something changed: for example when a signal that the template reads changes, or when a template event handler runs. Signals are the dependable way to say "this value changed", especially for work that finishes later, such as a timer or a server response. Using them everywhere gives one simple rule: state that appears on screen lives in a signal.
Try it yourself
- Add a "Double" button that multiplies the count by two.
- Create a
messagesignal of typestringand show it above the counter. Add a button that changes it. - Create a
namessignal holding an array of strings, and add a button that appends a name.
Quick check
- How do you read the value of a signal called
count? - When would you use
update()instead ofset()? - Why should you not call
pushon an array held in a signal?
Next, continue with Angular Styling: Global Styles, Component Styles, View Encapsulation, Bootstrap and Font Awesome.