Introduction
Angular Signals represent a revolutionary approach to state management and reactivity in Angular applications. Introduced in Angular 16 and expanded in subsequent versions, Signals offer a fine-grained reactivity model that provides better performance, improved developer experience, and more predictable data flow throughout your applications.
This article delves deep into Angular Signals — what they are, how they work, when to use them, and how they compare to traditional approaches.
What Are Angular Signals?
A Signal is a wrapper around a value that notifies interested consumers when that value changes. At its core, a Signal is a function that:
- Returns its wrapped value when called with no arguments
- Can be updated through related setter functions
- Automatically tracks dependencies and updates subscribers when changes occur
Signals represent a shift from Angular’s traditional change detection mechanism toward a more explicit and efficient reactivity model.
The Anatomy of Signals
Creating Signals
There are three primary ways to create signals:
- signal() — Creates a writable signal that can be updated directly:
import { signal } from '@angular/core';
// Create a signal with an initial value
const count = signal(0);
// Read the value
console.log(count()); // 0
// Update the value
count.set(1);
console.log(count()); // 1
// Update based on previous value
count.update(value => value + 1);
console.log(count()); // 2
- computed() — Creates a read-only signal derived from other signals:
import { signal, computed } from '@angular/core';
const firstName = signal('John');
const lastName = signal('Doe');
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // "John Doe"
// When a dependency changes, the computed value updates automatically
firstName.set('Jane');
console.log(fullName()); // "Jane Doe"
Signal API
The Signal API includes several key methods:
- set(newValue): Directly sets a new value
- update(updateFn): Updates based on the current value
- asReadonly(): Creates a read-only version of a writable signal
Using Signals in Components
Signals simplify component state management:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<h1>Counter: {{ count() }}</h1>
<p>Double value: {{ doubleCount() }}</p>
<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
`,
})
export class CounterComponent {
count = signal(0);
doubleCount = computed(() => this.count() * 2);
increment() {
this.count.update(value => value + 1);
}
decrement() {
this.count.update(value => value - 1);
}
}
Signal-based Inputs
Angular 17+ introduced signal-based inputs which provide reactive component inputs:
import { Component, input } from '@angular/core';
@Component({
selector: 'app-greeting',
template: `<h1>Hello, {{ name() }}!</h1>`,
standalone: true
})
export class GreetingComponent {
// Signal-based input
name = input<string>('Guest');
// Optional with required value
userId = input.required<string>();
// With transform function
count = input(0, {
transform: (value: string | number) => Number(value)
});
}
Effect Functions
Effects provide a way to react to signal changes with side effects:
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-logger',
template: `
<button (click)="increment()">Increment</button>
`,
})
export class LoggerComponent {
count = signal(0);
constructor() {
// This effect runs initially and whenever count changes
effect(() => {
console.log(`Count changed to: ${this.count()}`);
// You might call analytics, update localStorage, etc.
});
}
increment() {
this.count.update(value => value + 1);
}
}
Effects are ideal for: 💫
- Logging 📝
- Persisting data to localStorage
- Syncing with external systems
- Managing subscriptions
Signal Collections
Working with arrays and objects is made easier with signal collections:
import { Component, signal } from '@angular/core';
interface Todo {
id: number;
text: string;
completed: boolean;
}
@Component({
selector: 'app-todo-list',
template: `
<ul>
<li *ngFor="let todo of todos()">
<input type="checkbox" [checked]="todo.completed"
(change)="toggleTodo(todo.id)">
{{ todo.text }}
<button (click)="removeTodo(todo.id)">Delete</button>
</li>
</ul>
`
})
export class TodoListComponent {
todos = signal<Todo[]>([
{ id: 1, text: 'Learn Signals', completed: false },
{ id: 2, text: 'Build an app', completed: false }
]);
addTodo(text: string) {
this.todos.update(todos => [
...todos,
{ id: Date.now(), text, completed: false }
]);
}
toggleTodo(id: number) {
this.todos.update(todos =>
todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}
removeTodo(id: number) {
this.todos.update(todos => todos.filter(todo => todo.id !== id));
}
}
Signal-based HTTP Requests
Signals work well with HTTP requests:
import { Component, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
<div *ngIf="isLoading()">Loading...</div>
<div *ngIf="error()">{{ error() }}</div>
<div *ngIf="user()">
<h2>{{ user()?.name }}</h2>
<p>{{ user()?.email }}</p>
</div>
`
})
export class UserProfileComponent {
http = inject(HttpClient);
user = signal<User | null>(null);
isLoading = signal(false);
error = signal<string | null>(null);
ngOnInit() {
this.fetchUser(1);
}
fetchUser(id: number) {
this.isLoading.set(true);
this.error.set(null);
this.http.get<User>(`https://api.example.com/users/${id}`)
.subscribe({
next: (data) => {
this.user.set(data);
this.isLoading.set(false);
},
error: (err) => {
this.error.set('Failed to load user');
this.isLoading.set(false);
}
});
}
}
Performance Benefits
Signals provide several performance advantages:
- Fine-grained Updates : Only components that actually consume a changed signal are updated, not the entire component tree.
- Reduced Change Detection Cycles : By explicitly tracking dependencies, Angular can avoid checking components that aren’t affected by changes.
- Improved Memory Usage : The dependency tracking is more efficient than previous approaches.
- Zone.js Independence : Signals can work without Zone.js, allowing for a more lightweight application.
Signals vs RxJS: When to Use Each
Signals and RxJS serve different purposes but can complement each other:
Press enter or click to view image in full size

Migrating from Traditional Angular to Signals
Migrating to signals can be done incrementally:
- Start with New Components: Apply signals in new components first
- Identify State Management: Look for components using NgRx, services with BehaviorSubjects, etc.
- Replace @Input() Properties: Consider using signal-based inputs
- Refactor Services: Convert BehaviorSubjects to signals
- Update Templates: Ensure you call signal functions in templates with parentheses
Best Practices for Angular Signals
- Keep Signal Logic Simple: Avoid complex computations inside computed signals
- Use Computed for Derived State: Don’t duplicate state that can be derived
- Prefer Immutability: Use
.update()
with new objects. - Clean Up Effects: Make sure to properly destroy effects when components are destroyed
- Signal Naming: Consider ending signal variables with
$
to distinguish them
Common Pitfalls and Solutions
Pitfall 1: Forgetting to Call Signals
// Wrong ❌
<div>{{ count }}</div>
// Correct ✅
<div>{{ count() }}</div>
Pitfall 2: Mutating Signal Values Directly
// Wrong ❌
const user = signal({ name: 'John' });
user().name = 'Jane'; // Won't trigger updates!
// Correct ✅
user.update(u => ({ ...u, name: 'Jane' }));
Pitfall 3: Creating Computed Signals in Component Methods
// Wrong ❌ - will create a new computed on each render
ngOnInit() {
const doubleCount = computed(() => this.count() * 2);
}
// Correct ✅ - create computed as a class property
doubleCount = computed(() => this.count() * 2);
Conclusion
Angular Signals represent a significant evolution in how Angular applications handle reactivity and state management. They provide a more explicit, efficient, and developer-friendly approach that aligns with modern front-end development practices.
As Angular continues to evolve, Signals will likely become the primary way to handle reactivity in applications, gradually replacing older patterns such as RxJS for simple UI state and traditional change detection. By understanding and adopting Signals now, you’ll be well-positioned to build more performant and maintainable Angular applications in the future. 🚀✨