Post

Modern Angular 05: Introducing Signals for Local State

Modern Angular 05: Introducing Signals for Local State

This is lesson 5 of the Modern Angular Course. In the previous lesson, we worked with component templates and learned how to bind data and handle user interaction. So far, we have been using regular class properties to manage component state. Now it is time to introduce signals — the foundation of state management in modern Angular.

This post is part of the Modern Angular Course series. Check the course page for the full episode list.

In this post, we cover:

  • What problem signals solve and why Angular introduced them
  • The different types of signals
  • How to create and update a writable signal
  • How to use signals in templates
  • How signals compare to regular class properties

What Problem Signals Solve

Before we look at syntax, let’s talk about why signals exist.

Historically, Angular kept the UI in sync using change detection, and for a long time this was closely tied to Zone.js. Zone.js works by patching asynchronous browser APIs — things like promises, timers, DOM events, and HTTP requests. Whenever something asynchronous happened, Angular would run change detection and update the UI.

This approach worked, but it also meant:

  • Angular often had to guess when state might have changed
  • Change detection could run more often than necessary
  • It was not always obvious what actually triggered an update

As developers, we adapted to this model. For simple components, we used plain class properties and relied on Angular to detect changes. As applications grew, many of us started using RxJS more heavily — Observable, Subject, BehaviorSubject — even for local UI state that was synchronous.

This led to patterns like subscribing in components, managing subscriptions manually, using the async pipe everywhere, and turning simple counters or toggles into streams. It worked, but it added complexity and boilerplate.

Signals were introduced to solve this gap.

With signals:

  • State changes are explicit
  • Dependencies are tracked automatically
  • Angular knows exactly what depends on what

Instead of relying on Zone.js to notice that “something might have changed”, Angular reacts directly to declared state changes. This is also why Zone.js is being phased out and removed from the default Angular model.

Signals Do Not Replace RxJS

This is an important point. RxJS is still the right tool for:

  • HTTP requests (for now)
  • Streams of events
  • WebSockets
  • Complex asynchronous workflows

Signals shine when dealing with local, synchronous UI state — and that is exactly where we will start.

Signal Types Overview

Angular provides three types of signals:

  • Writable signals — state you can change directly
  • Computed signals — derived state based on other signals
  • Effects — logic that reacts to signal changes

In this post, we focus only on writable signals. Computed signals and effects will come in the next lessons, once the basics feel natural.

Creating Your First Writable Signal

Let’s start with a simple example. In our Hello component, instead of using a regular property, we create a writable signal:

1
2
3
4
5
6
7
8
9
10
11
12
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-hello',
  imports: [],
  templateUrl: './hello.html',
  styleUrl: './hello.scss',
})
export class Hello {

  protected count = signal(0);
}

This creates a writable signal with an initial value of 0. Notice we import signal from @angular/core — it is a core Angular primitive, not a third-party library.

To read the signal’s value, you call it like a function:

1
this.count(); // returns 0

This is a key part of the signals mental model. Signals are functions, not properties. You call them to read the current value.

Updating a Writable Signal

To update a signal, you do not assign a new value directly. Instead, you use set or update.

Using update

The update method takes a function that receives the current value and returns the new value. This is useful when the new value depends on the previous one:

1
2
3
4
5
6
7
increaseCounter() {
  this.count.update(value => value + 1);
}

decreaseCounter() {
  this.count.update(value => value - 1);
}

Using set

The set method replaces the current value entirely. This is useful when you want to set a specific value regardless of what was there before:

1
2
3
resetCounter() {
  this.count.set(0);
}

Both methods make state changes explicit and intentional. Angular now knows exactly when state changes and what needs to update.

Using Signals in Templates

Now let’s connect the signal to the template. Just like in the component class, we call the signal in the template:

1
2
3
4
5
<h1>{{ count() }}</h1>

<button (click)="increaseCounter()">+</button>
<button (click)="decreaseCounter()">-</button>
<button (click)="resetCounter()">Reset</button>

Angular tracks this dependency automatically. When the signal changes, Angular updates only the parts of the DOM that depend on it.

The Complete Component

Here is the full component with signals added alongside the bindings from the previous lesson:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-hello',
  imports: [],
  templateUrl: './hello.html',
  styleUrl: './hello.scss',
})
export class Hello {

  protected title = 'Welcome to Modern Angular!';

  protected isDisabled = false;

  protected onClick() {
    console.log('Button clicked');
    this.isDisabled = !this.isDisabled;
  }

  protected count = signal(0);

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

  decreaseCounter() {
    this.count.update(value => value - 1);
  }

  resetCounter() {
    this.count.set(0);
  }
}

And the full template:

1
2
3
4
5
6
7
8
9
10
11
12
<p>This is my first Angular component!</p>

<h1>{{ title }}</h1>

<button [disabled]="isDisabled"
  (click)="onClick()">Toggle</button>

<h1>{{ count() }}</h1>

<button (click)="increaseCounter()">+</button>
<button (click)="decreaseCounter()">-</button>
<button (click)="resetCounter()">Reset</button>

Notice the difference: title is a regular property read with {{ title }}, while count is a signal read with {{ count() }}. The parentheses tell Angular this is a reactive dependency.

Signals vs Regular Properties

At this point, you might wonder why not just keep using regular properties. For very small examples, the difference might feel minimal. But signals:

  • Make state reactive by default — Angular knows exactly what changed
  • Make dependencies explicit — you can trace what depends on what
  • Scale better as applications grow — no more guessing about change detection

This becomes especially valuable in larger components and real-world applications. And once we introduce computed signals in the next lesson, the benefits become even clearer.

Source Code

The full source code for the course is available on GitHub: loiane/modern-angular.

Next Step

In the next lesson, we introduce computed signals — derived state that updates automatically when its dependencies change. We will see how to avoid duplicated logic and keep components easier to maintain.

Watch the Video

This post is part of the Modern Angular Course series. Check the course page for the full episode list.

This post is licensed under CC BY 4.0 by the author.
This site uses cookies. Please choose whether to accept analytics cookies. Privacy Policy