In this post, we will see what Signals are and how we can use them in our Angular projects.
But first of all, what is a Signal?
“A Signal is a wrapper around a value that can notify interested consumers when that value changes.
It sits at the core of Angular’s modern reactivity model and gives us a consistent programming model for state management: writable states, computed derivations, and side effects.
Instead of wiring state updates manually (and differently) for each component, we get one abstraction and a set of proven defaults.”
Unlike a standard variable, a Signal tracks where it is being used. When the value inside the Signal changes, Angular knows exactly which parts of the UI need to be updated without checking the entire component tree.
WHEN SHOULD WE USE SIGNALS?
We should consider Signals when we’re building systems that require high performance and synchronous reactive state, where immediate UI updates are necessary and we can benefit from granular rendering. For example, when a user interacts with a complex data grid that needs instant recalculations, or when a shopping cart needs to trigger total updates and notification badges across the screen.
On the other hand, we must be careful not to use it when it is probably overkill: if we have a tiny, static component with no interactive state, traditional class properties might be enough.
THE BENEFITS TO USE SIGNALS
- Predictability: No more wondering when change detection will run.
- Performance: Updates are local and targeted, reducing CPU usage.
- Simplicity: They are much easier to manage than complex RxJS BehaviorSubject chains for simple synchronous state.
- No Memory Leaks: Unlike Observables, we don’t need to manually “unsubscribe” from Signals in our templates.
Let’s see some practical examples of how we can use them:
[Writable Signals]
This is the most basic form, where we can manually update the value.
ng g c components/CounterComponent
[counter-component.ts]
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter-component',
imports: [],
templateUrl: './counter-component.html',
styleUrl: './counter-component.css',
})
export class CounterComponent {
// We initialize the signal with 0
count = signal(0);
increment() {
// We update the value using .update()
this.count.update(value => value + 1);
}
reset() {
// We set a specific value using .set()
this.count.set(0);
}
}
[counter-component.html]
<div>
<!-- We call the signal as a function to get the value -->
<p>Current Count: {{ count() }}</p>
<button (click)="increment()">+1</button>
<button (click)="reset()">Reset</button>
</div>



[Computed Signals]
We should use when we have a value that depends on other signals.
They are read-only and update automatically.
ng g c components/ShoppingCartComponent
[shopping-cart.component.ts]
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-shopping-cart-component',
imports: [],
templateUrl: './shopping-cart-component.html',
styleUrl: './shopping-cart-component.css',
})
export class ShoppingCartComponent {
price = signal(100);
quantity = signal(1);
// This updates whenever price() or quantity() changes
total = computed(() => this.price() * this.quantity());
addQuantity() {
this.quantity.update(q => q + 1);
}
}
[shopping-cart.component.html]
<div class="cart">
<p>Price: ${{ price() }}</p>
<p>Quantity: {{ quantity() }}</p>
<hr>
<strong>Total: ${{ total() }}</strong>
<button (click)="addQuantity()">Add one more</button>
</div>



[Effects]
Effects are used for “side effects,” like logging or saving to LocalStorage when a signal changes.
ng g c components/UserSettings
[user-settings.ts]
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-user-settings',
imports: [],
templateUrl: './user-settings.html',
styleUrl: './user-settings.css',
})
export class UserSettings {
theme = signal('light');
constructor() {
// This effect runs every time theme() changes
effect(() => {
console.log(`Theme changed to: ${this.theme()}`);
localStorage.setItem('user-theme', this.theme());
});
}
toggleTheme() {
this.theme.update(t => t === 'light' ? 'dark' : 'light');
}
}
[user-settings.html]
<div [class]="theme()">
<p>The current theme is: {{ theme() }}</p>
<button (click)="toggleTheme()">Toggle Dark Mode</button>
</div>



[Advanced: Signals with Arrays]
When working with objects or arrays, it’s important to remember that Signals track the reference.
ng g c components/AppTodo
[app-todo.ts]
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-app-todo',
imports: [],
templateUrl: './app-todo.html',
styleUrl: './app-todo.css',
})
export class AppTodo {
todos = signal<string[]>(['Learn Angular', 'Write Blog Post']);
addTodo(newTodo: string) {
// We create a new array to trigger the signal update
this.todos.update(currentTodos => [...currentTodos, newTodo]);
}
}
[app-todo.html]
<ul>
@for (item of todos(); track item) {
<li>{{ item }}</li>
}
</ul>
<input #todoInput type="text">
<button (click)="addTodo(todoInput.value); todoInput.value = ''">
Add Task
</button>



