Angular Signals

Angular Signals implementation for automated reactive state management and performance-driven integration

Angular Signals is a community skill for reactive state management using Angular signals, covering signal creation, computed signals, effects, signal-based inputs, model signals, and migration patterns from RxJS to signals.

What Is This?

Overview

Angular Signals provides patterns for managing reactive state using Angular's signal primitives. It covers signal creation with typed writable signals that notify consumers when values change, computed signals that derive values from other signals with automatic dependency tracking, effects that execute side effects in response to signal changes for logging, persistence, or external synchronization, signal-based inputs using the input() function for reactive component properties that replace @Input decorators, and model signals with two-way binding support for parent-child state synchronization. The skill enables developers to write reactive Angular code with a simpler mental model than RxJS observables for component-level state management.

Who Should Use This

This skill serves Angular developers adopting signals for component state management, teams migrating from RxJS-heavy patterns to signal-based reactivity, and engineers building performant change detection with signal-driven rendering.

Why Use It?

Problems It Solves

RxJS observables require subscription management and async pipe usage that adds boilerplate. Change detection runs on entire component trees when using default strategy. Component inputs using @Input decorators lack reactive notification when values change. Derived state computation needs manual recalculation without automatic dependency tracking.

Core Highlights

Signals provide synchronous reactive primitives with automatic dependency tracking. Computed signals recalculate only when dependencies change. Signal-based inputs replace decorators with reactive function calls. Effects execute side effects with automatic cleanup on component destroy.

How to Use It?

Basic Usage

import { Component, signal,
  computed, effect } from
    '@angular/core';

@Component({
  selector: 'app-counter',
  standalone: true,
  template: `
    <h2>Count: {{ count() }}</h2>
    <p>Double: {{ double() }}</p>
    <p>Is even:
      {{ isEven() }}</p>
    <button
      (click)="increment()">
      +1</button>
    <button
      (click)="decrement()">
      -1</button>
    <button
      (click)="reset()">
      Reset</button>
  `,
})
export class
    CounterComponent {
  count = signal(0);
  double = computed(
    () => this.count() * 2);
  isEven = computed(
    () => this.count() % 2
      === 0);

  constructor() {
    // Log changes
    effect(() => {
      console.log(
        `Count changed:`
        + ` ${this.count()}`);
    });
  }

  increment() {
    this.count.update(
      (c) => c + 1);
  }

  decrement() {
    this.count.update(
      (c) => c - 1);
  }

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

Real-World Examples

import { Component, signal,
  computed, input, model }
  from '@angular/core';

// Signal-based store
interface Todo {
  id: number;
  text: string;
  done: boolean;
}

@Component({
  selector: 'app-todo',
  standalone: true,
  template: `
    <input [value]="newText()"
      (input)="newText.set(
        $event.target.value)" />
    <button
      (click)="add()">
      Add</button>

    <p>{{ remaining() }}
      remaining</p>

    @for (todo of todos();
        track todo.id) {
      <label>
        <input type="checkbox"
          [checked]="todo.done"
          (change)="toggle(
            todo.id)" />
        {{ todo.text }}
      </label>
    }
  `,
})
export class TodoComponent {
  todos = signal<Todo[]>([]);
  newText = signal('');
  nextId = signal(1);

  remaining = computed(
    () => this.todos()
      .filter(
        (t) => !t.done).length);

  add() {
    const text =
      this.newText().trim();
    if (!text) return;

    this.todos.update(
      (list) => [
        ...list,
        { id: this.nextId(),
          text, done: false },
      ]);
    this.nextId.update(
      (n) => n + 1);
    this.newText.set('');
  }

  toggle(id: number) {
    this.todos.update(
      (list) =>
        list.map((t) =>
          t.id === id
            ? { ...t,
                done: !t.done }
            : t));
  }
}

Advanced Tips

Use toSignal() from @angular/core/rxjs-interop to bridge RxJS observables into signals for gradual migration. Implement effect() with allowSignalWrites for effects that need to update other signals in response to changes. Use linkedSignal for signals whose value resets when a source signal changes.

When to Use It?

Use Cases

Build a reactive form component with computed validation state derived from input signals. Create a todo application with signal-based state and computed filtered views. Migrate an RxJS BehaviorSubject store to signal-based state management.

Related Topics

Angular signals, computed, effects, reactive state, and RxJS interop.

Important Notes

Requirements

Angular 17 or later for signal inputs and model signals. Angular 16 for basic signal, computed, and effect primitives. Understanding of reactive programming concepts.

Usage Recommendations

Do: use computed signals for derived state rather than recalculating in templates. Prefer signal.update() for immutable state transitions on objects and arrays. Use toSignal and toObservable for bridging between signals and RxJS when both are needed.

Don't: create effects that read many signals causing frequent unnecessary executions. Use signals for async data fetching where RxJS observables handle cancellation better. Mutate signal values directly instead of using update with a new reference.

Limitations

Signals are synchronous and do not replace RxJS for async stream processing. Effect cleanup requires returning a cleanup function pattern that differs from RxJS teardown. Signal equality checks use reference comparison by default requiring custom equal functions for deep comparison.