Angular Signals: The Future of Reactivity

Angular Signals: The Future of Reactivity

Angular Signals: The Future of Reactivity

Angular Signals (stable since Angular 17) fundamentally change how Angular tracks and responds to state changes. Instead of running change detection across the whole component tree, signals allow Angular to know exactly which components depend on which state.

Step 1 — Creating Signals

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

// Writable signal
const count = signal(0);

// Read a signal — call it like a function
console.log(count()); // 0

// Update signals
count.set(5);
count.update(c => c + 1);

Step 2 — Computed Signals

const firstName = signal('Alice');
const lastName  = signal('Smith');

// Automatically updates when firstName or lastName changes
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // Alice Smith

firstName.set('Bob');
console.log(fullName()); // Bob Smith

Step 3 — Effects

import { effect } from '@angular/core';

export class AppComponent {
  count = signal(0);

  constructor() {
    // Runs immediately and whenever count changes
    effect(() => {
      console.log(`Count changed to: ${this.count()}`);
      // Save to localStorage, send analytics, etc.
    });
  }
}

Step 4 — Signals in Services (Shared State)

@Injectable({ providedIn: 'root' })
export class ThemeService {
  private _theme = signal<'light' | 'dark'>('light');

  readonly theme    = this._theme.asReadonly(); // expose read-only
  readonly isDark   = computed(() => this._theme() === 'dark');

  toggle(): void {
    this._theme.update(t => t === 'light' ? 'dark' : 'light');
  }
}

Step 5 — Signal Inputs (Angular 17.1+)

import { input, output } from '@angular/core';

@Component({ selector: 'app-counter', standalone: true, template: `...` })
export class CounterComponent {
  // Signal-based input — no more @Input decorator
  initialValue = input(0);
  label        = input.required();

  // Signal-based output
  changed = output();

  count = computed(() => this.initialValue());
}
All Comments