Reactivity in Angular is the process by which the framework determines that the application state has changed and, if necessary, refreshes the view or the DOM. If you’ve read the article Change detection strategy, the magic of Angular, you’ll know that Angular bases this detection on a component tree and mechanisms such as NgZone or ChangeDetectionStrategy to control when and how the UI is updated.
Today we’ll go one step further: we’ll see how Angular Signals, the new reactivity API in Angular introduced experimentally in Angular 16, makes it possible to improve UI efficiency, update only what is necessary, and simplify state management in Angular components, optimizing the performance of your applications.
The problem with classic change detection in Angular
In the traditional approach:
- Each component has its own change detector.
- Angular traverses the component tree every time an asynchronous event occurs (click, timer, HTTP request).
- In applications with many components or large lists, this creates unnecessary change detection cycles, affecting performance.
For example, in the previous article we created a clock that updates every second and a table of users. The clock update triggered a full Angular change detection cycle, recalculating random values in each row, even though the data had not changed.
Angular Signals: how they improve reactivity
With Angular Signals:
- Each signal represents a reactive value.
- Angular updates only the bindings that depend on that signal.
- Derived values (computed) are recalculated automatically.
- There is no need for ChangeDetectionStrategy.OnPush or ChangeDetectorRef to optimize performance.
This makes Signals a key tool for improving performance in Angular, especially in large applications with lists and complex components.
Signals, effects, and derived values
To better understand it, let’s see how Signals work in Angular.
Create a signal
import { signal } from '@angular/core';
const counter = signal(0);
- A signal represents a reactive value.
- To read it, it is invoked as a function: counter().
- To write to it, .set() or .update() is used.
Effects (effect)
Effects are functions that run automatically when the signals they use change.
import { signal, effect } from '@angular/core';
const counter = signal(0);
effect(() => {
console.log(`The counter value is ${counter()}`);
});
counter.set(1); // The effect runs and prints “The counter value is 1”
Computed values
You can compute a value from one or more signals without having to rewrite additional logic.
import { computed } from '@angular/core';
const double = computed(() => counter() * 2);
- When counter changes, Angular automatically recalculates double.
- computed is read-only and cannot be modified manually.
Advanced features of Angular Signals
In addition to what we have seen, Angular Signals includes several important features, according to the official documentation:
Computed is lazy and memoized
- It does not execute until someone reads it.
- It memorizes its last value.
- It only recalculates if one of its dependencies changes.
const counter = signal(0);
const double = computed(() => {
console.log('Recalculando...');
return counter() * 2;
});
- If you never call double(), the function is not executed.
Dynamic dependency tracking
Angular only tracks signals that are actually read within a computed or effect.
const show = signal(false);
const counter = signal(0);
const conditional = computed(() => show() ? counter() : 0);
- As long as show() is false, conditional does not depend on counter.
- This optimizes performance and avoids unnecessary recalculations.
Signals in templates
{{ counter() }}
- Angular automatically detects that the template depends on the signal.
- The UI updates only when the value changes.
- You don't need async pipe, ChangeDetectorRef, or markForCheck.
untracked(): avoid reactive dependencies
Allows you to read a signal without registering it as a dependency within an effect or computed.
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);
});
- The effect runs only when currentUser changes.
- Changes in counter do not trigger the effect, but we can still read its current value.
- This is useful when you want to read data incidentally without turning it into a trigger for reactivity.
Live practical example: clock and user table with Angular Signals

Instead of showing all the code here, we’ve prepared an interactive example on StackBlitz so you can see Angular Signals working in real time.
In this example you will observe:
- A clock that updates every second.
- A user table where each row depends on a signal, avoiding unnecessary updates.
- How the UI updates only when the relevant state changes, improving performance compared to classic change detection.
Try the live example on StackBlitz.
Visual comparison
| Before (Default CD) | Now (Signals) |
|---|---|
| Full change detection cycle every second | Only what depends on a signal is updated |
| randomId was recalculated unnecessarily | randomId remains stable |
| OnPush required for optimization | No need for OnPush or ChangeDetectorRef |
| Performance affected with many rows | Performance remains stable even when the clock updates |
Conclusion
Angular Signals represents the evolution of the change detection model explained in our previous article.
Previously, any asynchronous event could trigger a full change detection cycle. Now, Angular updates only the bindings that depend on the signals that change, avoiding unnecessary computations and improving efficiency.
With Signals and derived values (computed), we can build faster, more predictable, and easier-to-maintain applications, especially as the component tree grows.
Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.
Tell us what you think.