Angular Signals: Reactive State for Beginners

Angular Tutorial: Beginner to Advanced · 3. Components and templates · 4 min read · Updated

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() and update()
  • 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

  1. Add a "Double" button that multiplies the count by two.
  2. Create a message signal of type string and show it above the counter. Add a button that changes it.
  3. Create a names signal holding an array of strings, and add a button that appends a name.

Quick check

  1. How do you read the value of a signal called count?
  2. When would you use update() instead of set()?
  3. Why should you not call push on an array held in a signal?

Next, continue with Angular Styling: Global Styles, Component Styles, View Encapsulation, Bootstrap and Font Awesome.

Frequently asked questions

Do I still need to know about change detection?

Only the basics. If you keep screen state in signals, Angular handles the updates. You will rarely deal with change detection directly as a beginner.

Are signals the same as RxJS observables?

No. Signals hold a current value that you read synchronously. Observables describe streams of values over time. They work together, and you will meet observables in the HTTP module.