Signals Basics (v16)

📚 What are Signals?

Signals are Angular's reactive primitive for state management:

  • Reactive: UI updates automatically when signal value changes
  • Fine-grained: Only affected parts of UI update
  • Synchronous: Values are read synchronously (not like observables)
  • Simple API: signal(), get(), set(), update()
  • No subscriptions: No memory leaks from forgotten unsubscribes

📅 Signals Version History

v16Signals introduced (Developer Preview)
v17signal(), computed(), effect() stable
v18input(), output(), model() stable
v19resource(), linkedSignal() added

🎯 Interview Questions

  • Q1: What is the difference between signals and observables?
  • A: Signals are synchronous and always have a value. Observables are async streams. Signals don't need subscriptions/unsubscriptions.
  • Q2: How do you read a signal value?
  • A: Call the signal as a function: mySignal(). In templates: {{ mySignal() }}
  • Q3: What is the difference between set() and update()?
  • A: set(newValue) replaces entirely. update(fn) takes previous value: update(prev => prev + 1)
  • Q4: How do signals improve performance?
  • A: Fine-grained reactivity means only components reading changed signals update, not entire component tree.

🔧 Live Demo - Signal Operations

Basic Counter Signal

0

Object Signal

Name: John

Age: 30

Array Signal

  • Apple
  • Banana
  • Cherry

Read-only Signal (asReadonly)

Read-only count: 0

asReadonly() creates a read-only view - useful for exposing state publicly

📋 Signal API Reference

MethodDescriptionExample
signal()Create writable signalsignal(0)
mySignal()Read signal valuecount()
.set(value)Set new valuecount.set(10)
.update(fn)Update based on previouscount.update(c => c + 1)
.asReadonly()Create read-only viewcount.asReadonly()

💻 Signals Code


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

// ===== Creating Signals =====

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

// Object signal
const user = signal({ name: 'John', age: 30 });

// Array signal
const items = signal<string[]>([]);

// ===== Reading Signals =====

// Call signal as function to read
const currentCount = count(); // 0
const userName = user().name; // 'John'

// In template
// {{ count() }}
// {{ user().name }}

// ===== Updating Signals =====

// set() - replace value entirely
count.set(10);
user.set({ name: 'Jane', age: 25 });

// update() - based on previous value
count.update(c => c + 1);
user.update(u => ({ ...u, age: u.age + 1 }));
items.update(arr => [...arr, 'New Item']);

// ===== Read-only Signals =====

// Expose read-only version publicly
private readonly _count = signal(0);
readonly count = this._count.asReadonly();

// Outside code can read but not modify
console.log(this.count()); // ✅ Works
// this.count.set(5);      // ❌ Error - no set method

// ===== Signal Options =====

// Custom equality function
const obj = signal({ a: 1 }, {
  equal: (prev, curr) => prev.a === curr.a
});

// ===== Signals vs BehaviorSubject =====

// BehaviorSubject (RxJS)
const subject = new BehaviorSubject(0);
subject.subscribe(v => console.log(v)); // Need subscription
subject.next(1);
subject.unsubscribe(); // Need cleanup

// Signal (Angular)
const sig = signal(0);
console.log(sig()); // Just read, no subscription!
sig.set(1);
// No cleanup needed!