Signals in Angular
What are Signals?
Signals (introduced in Angular 16) are a new reactive primitive for managing state. They provide fine-grained reactivity β Angular knows exactly what changed and updates only the affected parts of the UI.
Basic Usage
typescript
import { signal, computed, effect } from '@angular/core';
@Component({
template: \`
<p>Count: {{ count() }}</p>
<p>Double: {{ doubleCount() }}</p>
<button (click)="increment()">+</button>
<button (click)="reset()">Reset</button>
\`
})
export class CounterComponent {
// Writable signal
count = signal(0);
// Computed signal (derived, read-only)
doubleCount = computed(() => this.count() * 2);
increment() {
// Set new value
this.count.set(this.count() + 1);
// Or update based on previous value
this.count.update(c => c + 1);
}
reset() {
this.count.set(0);
}
}signal() β Writable Signal
typescript
const name = signal('Alice');
// Read
console.log(name()); // "Alice"
// Write
name.set('Bob');
// Update based on current value
name.update(current => current.toUpperCase());
// Mutate (for objects/arrays β mutates in place)
const users = signal([{ name: 'Alice' }]);
users.mutate(arr => arr.push({ name: 'Bob' }));computed() β Derived Signal
typescript
const firstName = signal('John');
const lastName = signal('Doe');
// Automatically recalculates when dependencies change
const fullName = computed(() => \`\${firstName()} \${lastName()}\`);
console.log(fullName()); // "John Doe"
firstName.set('Jane');
console.log(fullName()); // "Jane Doe"
// computed signals are READ-ONLY
// fullName.set('X'); // β Error!effect() β Side Effects
typescript
const count = signal(0);
// Runs whenever count changes
effect(() => {
console.log('Count changed to:', count());
// Log, save to localStorage, API call, etc.
});
count.set(5); // Logs: "Count changed to: 5"Signals vs Observables
| Feature | Signals | Observables (RxJS) |
|---|---|---|
| Synchronous | β Always has value | β May be async |
| Current value | β
signal() | β οΈ Need BehaviorSubject |
| Auto-cleanup | β No subscriptions | β Must unsubscribe |
| Template usage | {{ signal() }} | {{ obs$ | async }} |
| Operators | β None | β Rich operators |
| Streams/Events | β Not designed for | β Built for streams |
| Change detection | Fine-grained | Zone-based |
Input Signals (Angular 17+)
typescript
@Component({ /* ... */ })
export class UserCard {
// Signal-based inputs
name = input.required<string>();
age = input(0); // With default value
role = input<'admin' | 'user'>('user');
// Computed from input signals
isAdult = computed(() => this.age() >= 18);
}html
<app-user-card [name]="'Alice'" [age]="25" />Practical Example: Shopping Cart
typescript
@Component({ /* ... */ })
export class CartComponent {
items = signal<CartItem[]>([]);
totalPrice = computed(() =>
this.items().reduce((sum, item) => sum + item.price * item.quantity, 0)
);
itemCount = computed(() =>
this.items().reduce((sum, item) => sum + item.quantity, 0)
);
addItem(item: CartItem) {
this.items.update(items => [...items, item]);
}
removeItem(id: string) {
this.items.update(items => items.filter(i => i.id !== id));
}
}Important:
Signals are Angular's modern reactivity system β simpler than RxJS for synchronous state. Use signal() for state, computed() for derived values, and effect() for side effects. They enable fine-grained change detection (no Zone.js needed). Keep using RxJS for async streams (HTTP, WebSocket, events). Signals and Observables work together.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.