Rethinking Reactivity in Angular: A Deep Dive into Angular Signals

Today, I’m hyped to share some game-changing updates in Angular’s approach to change detection. Though it has been in development for a while in Angular 16 in pre-release—I only recently started playing around with it in Angular 17. Traditionally, Angular monitors changes across the entire application — though efficient, however, potentially wasteful. Imagine if Angular could pinpoint specific state changes, updating only where needed. With Angular Signals this future is closer than ever, a feature that offers fine-grained reactivity for better performance and scalability.
Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates. // Angular Dev Guide, https://angular.dev/guide/signals
At its core, change detection is how Angular keeps your app’s UI in sync with its data — how angular updates the UI in the browser, when the state in the app changes, from either new data or an interaction with the user. Making something happen when there is a change is called Reactivity. Angular’s reactivity in the past would run change detection all over the entirety of the application.
What is Reactivity in Angular?
Reactivity enables Angular to update the UI based on changing data or user interactions. Traditionally, Angular’s reactivity isn’t precise about where changes occur, leading to a “whole app” update approach. But what if Angular could “know” exactly what changed and where? This idea is foundational to Angular Signals. If your state could tell Angular where exactly it is used and when it changes so Angular could automatically update whatever depends on that value. When working in a big application, changes and updates like this concatenate over a long period of time to create noticeable performance improvements for your application.
Introducing Angular Signals
Angular Signals include three new reactivity primitives: Signal, Computed, and Effect. Together, they help Angular focus only on parts of the UI that need updates.
- Signal — Think of it as a reactive variable. When a signal’s value changes, Angular knows where it’s used and updates the dependent UI automatically.
import { signal } from '@angular/core';
const count = signal(0);
// Updating the signal
count.set(count() + 1);
2. Computed — A signal that derives its value from other signals. Angular updates it only when the signals it depends on change, optimizing the refresh cycle.
import { computed } from '@angular/core';
const doubleCount = computed(() => count() * 2);
3. Effect — An effect triggers a specific function, when the signals it depends on change, allowing developers to handle updates meaningfully.
import { effect } from '@angular/core';
effect(() => {
console.log(`The count is ${count()}`);
});
Signals vs. RxJS
Angular Signals offer several advantages over RxJS in the context of Angular applications, especially regarding simplicity and readability
1. Simplicity: Signals are inherently simpler, offering a direct, intuitive syntax for managing reactivity without complex operators or subscriptions required by RxJS.
2. Fine-grained reactivity: Signals provide targeted, fine-grained reactivity, updating only specific parts of the UI tied to the data changes. This can lead to faster performance, especially in complex UIs.
3. Automatic Cleanup: Signals automatically manage lifecycle and cleanup, whereas RxJS requires manual subscription and unsubscription handling, reducing memory leaks. RxJS cleanup can often be a hassle, especially with unit testing.
4. Better Debugging and Readability: Signals use a more straightforward model, making reactive code easier to trace, debug, and reason about compared to RxJS streams, which can be complex to follow.
Together, these advantages make Angular Signals a powerful alternative for managing state in Angular applications. I’ll definitely continue using RxJS, however, I enjoyed learning more about and diving into angular signals.