DEV Community

Alex Spinov
Alex Spinov

Posted on

Angular Signals Have Changed Angular Forever — Here's the Complete Guide

Angular finally has fine-grained reactivity. Signals replace Zone.js and make change detection predictable and fast.

What Are Angular Signals?

Signals are reactive primitives that notify Angular when their value changes. Instead of Zone.js checking everything, Angular only updates what actually changed.

Creating Signals

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

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

  constructor() {
    effect(() => {
      console.log('Count changed:', this.count());
    });
  }

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

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

Signal API

// Create
const name = signal('Alice');
const user = signal({ name: 'Alice', age: 30 });
const items = signal<string[]>([]);

// Read
console.log(name()); // 'Alice'

// Set
name.set('Bob');

// Update (based on previous value)
items.update(prev => [...prev, 'new item']);

// Computed (derived)
const greeting = computed(() => `Hello, ${name()}!`);

// Effect (side effects)
effect(() => {
  document.title = `User: ${name()}`;
});
Enter fullscreen mode Exit fullscreen mode

Signal-Based Inputs

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

@Component({
  selector: 'app-user-card',
  template: `
    <div class="card">
      <h2>{{ name() }}</h2>
      <p>Age: {{ age() }}</p>
      <p *ngIf="email()">{{ email() }}</p>
    </div>
  `
})
export class UserCardComponent {
  name = input.required<string>();
  age = input.required<number>();
  email = input<string>(); // optional
}
Enter fullscreen mode Exit fullscreen mode

Signal-Based Outputs

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

@Component({
  selector: 'app-search',
  template: `<input (input)="onInput($event)">`
})
export class SearchComponent {
  searchChange = output<string>();

  onInput(event: Event) {
    const value = (event.target as HTMLInputElement).value;
    this.searchChange.emit(value);
  }
}
Enter fullscreen mode Exit fullscreen mode

Signal Store (NgRx SignalStore)

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

export const TodoStore = signalStore(
  withState({
    todos: [] as Todo[],
    loading: false,
    filter: 'all' as 'all' | 'active' | 'completed',
  }),
  withComputed((store) => ({
    filteredTodos: computed(() => {
      const todos = store.todos();
      const filter = store.filter();
      switch (filter) {
        case 'active': return todos.filter(t => !t.completed);
        case 'completed': return todos.filter(t => t.completed);
        default: return todos;
      }
    }),
    completedCount: computed(() => 
      store.todos().filter(t => t.completed).length
    ),
  })),
  withMethods((store) => ({
    addTodo(title: string) {
      patchState(store, (state) => ({
        todos: [...state.todos, { id: Date.now(), title, completed: false }],
      }));
    },
    toggleTodo(id: number) {
      patchState(store, (state) => ({
        todos: state.todos.map(t => 
          t.id === id ? { ...t, completed: !t.completed } : t
        ),
      }));
    },
  }))
);
Enter fullscreen mode Exit fullscreen mode

Before and After

// Before (Zone.js era)
@Component({ ... })
export class OldComponent implements OnInit {
  count = 0;
  doubled = 0;
  ngOnInit() { this.doubled = this.count * 2; }
  increment() { this.count++; this.doubled = this.count * 2; }
}

// After (Signals)
@Component({ ... })
export class NewComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2); // auto-updates!
  increment() { this.count.update(c => c + 1); }
}
Enter fullscreen mode Exit fullscreen mode

Building Angular dashboards with real data? Check out my Apify actors — structured data for any Angular app. For custom solutions, email spinov001@gmail.com.

Top comments (0)