Meet Angular Signals: State Management in Angular

yerinyerin
4 min read

What are Signals?

Introduced as an official feature in Angular 17, Signals provide a simple state management solution that automatically updates any connected parts of the application whenever their value changes.

Unlike external libraries, Signals are built directly into Angular’s core.

Key Concepts and Features

Features

  • Provides a straightforward API without the need for complex Observable patterns.

  • Supports a new change detection strategy that works without relying on Zone.js.

  • Before Signals: Angular used dirty checking via Zone.js, which checked all bindings across the component tree. This often led to performance issues.

  • With Signals: Only the parts directly tied to a changed Signal are updated, greatly improving efficiency.

  • Seamlessly integrates with Angular’s change detection mechanism.

Performance Benefits

  • Optimized Rendering: Only the necessary parts of the UI are re-rendered.

  • Better Memory Usage: Cuts down on unnecessary object creation.

  • Efficient CPU Usage: Reduces redundant calculations and checks.

  • Faster Responsiveness: UI updates happen more quickly.

Core Concept

  • Signal: A value that changes over time.
const count = signal(0);
  • Computed Signal: A derived value based on other signals.
  const doubleCount = computed(() => count() * 2)
  • Using this.count() * 2 directly can trigger recalculations during each change detection cycle. Computed signals, however, handle change detection and optimization automatically.
  • Effect: Runs side effects when signals change.
 effect(() => console.log('Count changed!:', count()))
  • Immutability: Signals are immutable — values can’t be changed directly, they must be replaced with a new value.

    • set: count.set(count() + 1)

    • update: count.update(prevCount => prevCount + 1)

    • set corresponds to directly setting a value in React, like setCount(2).

    • update corresponds to calculating a new state based on the previous state in React, like setCount(prev => prev + 1).

SignalStore

SignalStore allows you to manage application state in a centralized way, similar to NgRx but powered by Signals.

import { signalStore, withState, withComputed, withMethods } from '@ngrx/signals';

export const CounterStore = signalStore(
  { providedIn: 'root' },
  withState({ count: 0 }),

  withComputed(({ count }) => ({
    doubleCount: computed(() => count() * 2)
  })),

  withMethods(({ count, ...store }) => ({
    increment() {
      count.update(c => c + 1);
    },
    decrement() {
      count.update(c => c - 1);
    }
  }))

withHooks({
  onInit: (store) => {
    // Store Initialization Logic
  },
  onDestroy: (store) => {
    // Cleanup work
  }
})
);
  • withState: Define the initial state. Each property in the object is automatically converted to a Signal.

  • withComputed: Define computed values derived from the state.

  • withMethods: Define state-manipulating methods.

  • withHooks: Define the lifecycle logic for initialization and cleanup.

    • Lifecycle of the store?

    • The process of the store being created, used, and eventually removed.

    • Initialization onInit

      • Runs when the store is created or injected. Useful for setup tasks such as data fetching or subscriptions.
    • Active Stage

      • State updates, computed value recalculations, and method calls occur here.
    • Removal onDestroy

      • Runs when the store is no longer needed. Used for cleanup to prevent memory leaks.

Comparison with NgRx

  • Signals

    • Simple, intuitive, and easy to learn.

    • Best for small to medium-sized projects or rapid development.

    • Mainly focused on component-level state management.

  • NgRx

    • Provides powerful state management for large, complex apps.

    • Steeper learning curve and heavier boilerplate.

    • More complex initial setup and potentially larger bundle size.

    • Internally uses RxJS Observables to manage state.

Comparison with React Hooks

  • Signal ≈ useState

  • Computed Signal ≈ useMemo

  • Effect ≈ useEffect

Key Differences

  • Signals are defined as class properties and can be used outside of functional components. Their values are accessed by calling them like a function (e.g., count()).

  • Effects automatically track dependencies — no need for a dependency array. They also clean themselves up when a component is destroyed, so there’s no need to return a cleanup function. Typically, you set them up in the constructor or ngOnInit.

  • React requires manual optimization with useCallback, useMemo, etc. Signals, on the other hand, are optimized by default.

Conclusion

Since I’m already familiar with React, I found it easier to understand Signals by mapping them to React hooks. Once I made the connection, I realized they are even simpler than React’s equivalents.

Thus, I have published a total of 4 posts on understanding Angular:

  • Basics and core concepts of Angular

  • RxJS and reactive programming

  • Angular state management: A guide to using NgRx

  • New state management in Angular: Signals

👏

0
Subscribe to my newsletter

Read articles from yerin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

yerin
yerin