DEV Community

Alex Spinov
Alex Spinov

Posted on

Angular Signals Has a Free API — Here's How Reactive State Just Changed

TL;DR

Angular Signals is the new reactive primitive in Angular 16+. It replaces complex RxJS patterns for state management with a simpler, fine-grained reactivity system — and it's built right into Angular core.

What Are Angular Signals?

Angular Signals are a new reactive primitive that track values and automatically notify consumers when those values change:

  • Fine-grained reactivity — only re-render what actually changed
  • No Zone.js needed — opt-in zoneless change detection
  • Simpler than RxJS — for most state management use cases
  • Built-in — no additional packages required
  • TypeScript-first — full type inference

Basic Signals

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

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

// Read the value
console.log(count()); // 0

// Update the value
count.set(5);
count.update((val) => val + 1); // 6

// Computed signal (derived state)
const doubled = computed(() => count() * 2); // 12

// Effect (side effects on change)
effect(() => {
  console.log(`Count is now: ${count()}`);
});
Enter fullscreen mode Exit fullscreen mode

Signals in Components

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

@Component({
  selector: "app-counter",
  template: `
    <h2>Count: {{ count() }}</h2>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+1</button>
    <button (click)="reset()">Reset</button>
  `,
})
export class CounterComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

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

  reset() {
    this.count.set(0);
  }
}
Enter fullscreen mode Exit fullscreen mode

Signal-based Inputs (Angular 17.1+)

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

@Component({
  selector: "app-user-card",
  template: `
    <div class="card">
      <h3>{{ name() }}</h3>
      <p>Role: {{ role() }}</p>
      <button (click)="selected.emit(name())">Select</button>
    </div>
  `,
})
export class UserCardComponent {
  // Signal-based inputs — type-safe, no decorators
  name = input.required<string>();
  role = input<string>("viewer"); // with default

  // Signal-based output
  selected = output<string>();
}
Enter fullscreen mode Exit fullscreen mode

Signal Store (NgRx Signals)

import { signalStore, withState, withMethods, patchState } from "@ngrx/signals";

type TodoState = {
  todos: Todo[];
  loading: boolean;
  filter: "all" | "active" | "completed";
};

export const TodoStore = signalStore(
  withState<TodoState>({
    todos: [],
    loading: false,
    filter: "all",
  }),
  withMethods((store) => ({
    addTodo(title: string) {
      patchState(store, {
        todos: [...store.todos(), { id: Date.now(), title, completed: false }],
      });
    },
    toggleTodo(id: number) {
      patchState(store, {
        todos: store.todos().map((t) =>
          t.id === id ? { ...t, completed: !t.completed } : t
        ),
      });
    },
  }))
);
Enter fullscreen mode Exit fullscreen mode

Signals vs RxJS

Feature Signals RxJS
Learning curve Low Steep
Sync state ✅ Perfect Overkill
Async streams Use toSignal() ✅ Perfect
Memory management Automatic Manual (unsubscribe)
Template binding Direct {{ sig() }} Needs async pipe
Debugging Simple Complex

Migration: RxJS to Signals

import { toSignal, toObservable } from "@angular/core/rxjs-interop";

// Convert Observable to Signal
const users = toSignal(this.userService.getUsers(), {
  initialValue: [],
});

// Convert Signal to Observable (when you need RxJS operators)
const count$ = toObservable(this.count);
Enter fullscreen mode Exit fullscreen mode

Resources


Building data-driven Angular apps? My Apify web scraping tools can extract structured data from any website for your Angular dashboards. Need custom solutions? Email spinov001@gmail.com

Top comments (0)