In this tutorial we'll see what is signal in Angular and how it is a move towards reactive state management by Angular framework.
Signal in Angular
Angular Signals were officially released in Angular 17 and they provide fine-grained control over the changes in the component. Angular Signals optimize DOM changes by providing a fine-grained, reactive system for updating the UI. When a signal's value change, only those components, which are using that signal value, are notified, that allows Angular to update only the affected sections of the DOM making the process of reloading the component faster. This is an optimization over the full change detection cycle where the full component tree is scanned, starting from the root component and moving downwards.
How are signals defined
A signal is a wrapper around a value and it manages its state. When that value changes all the dependent components are notified. Signals can contain any value, from primitives to complex objects.
For example-
const count = signal(0);
You can create a signal by calling the signal function with the signal's initial value.
How to Read a Signal
You can read a value of the signal by calling the signal with parenthesis.
For example- count()
This calls the signal's getter function.
Types of signals in Angular
Angular signals can be of two types-
- Writable signals
- Read-only signals
Writable signals
You can create a writable signal by calling the signal function with the signal's initial value. This value can be modified later by using set() or update() function.
If you want to provide a new value to a writable signal, you can set it directly using .set()
count.set(5);
If you want to compute a new value using the previous one, then .update() is a better option.
// increment previous count value by 1 count.update(pv => pv + 1)
Read-only signals
Read-only or computed signals derive their value from other signals. These signals are defined using the computed function, you have to specify the derived value logic with in the computed function.
As example, if there is a Product object with properties as id, name, price and quantity and you want to calculate the total price based on the quantity and price of the product.
productSignal = signal<Product>(id:1, name:'Laptop', price:1000, quantity:5));
// Readable signal
totalPrice = computed(() => {
const product = this.productSignal();
return product.price * product.quantity;
});
Here totalPrice signal depends on the price and quantity of the product signal. Whenever the price or quantity updates, Angular knows that totalPrice needs to be updated as well.
As the name suggests, you cannot directly assign values to a read-only signal. Trying to do something like this-
totalPrice.set(5000);
results in a compilation error.
Note that read-only signals are both lazily evaluated and memoized (cached). The logic you have written to derive the value of a computed signal does not run to calculate its value until the first time you read that value.
The calculated value is then cached and the same value is returned when needed without recalculating. If you change any value which is used to calculate the read-only signal value then Angular knows that read-only signal's cached value is no longer valid and it calculates the new value when needed the next time.
Angular Signals example
1. In this simple example there is a count signal with initial value as 0. In the template there is a button, click of that button triggers an event to increment the count.
signaldemo.component.ts
import { Component, signal } from "@angular/core";
@Component({
selector:'app-signaldemo',
templateUrl: './signaldemo.component.html',
standalone:false
})
export class SignalDemoComponent{
count = signal(0);
increment(){
this.count.update(count => count+1);
console.log(this.count);
}
}
As you can see in the increment() method, count signal is updated.
src\app\signaldemo.component.html
<div>{{count()}}</div>
<button (click)="increment()">Increment</button>
2. This example demonstrates the use of read-only signal. There is a Product object with properties as id, name, price and quantity and you want to calculate the total price based on the quantity and price of the product.
models\product.model.ts
export class Product{
id: number;
name: string;
price: number;
quantity: number;
constructor(id: number, name: string, price: number, quantity: number){
this.id = id;
this.name = name;
this.price = price;
this.quantity = quantity;
}
}
Above class is used to create Product objects.
singleproduct.component.ts
import { Component, computed, signal } from "@angular/core";
import { Product } from "../models/product.model";
@Component({
selector:'app-productSingle',
templateUrl: './singleproduct.component.html',
standalone:false
})
export class SingleProductComponent{
productSignal = signal<Product>(new Product(1, 'Laptop', 1000, 1));
totalPrice = computed(() => {
const product = this.productSignal();
return product.price * product.quantity;
});
decrementQuantity():void{
this.productSignal.update((product) => {
if(product.quantity > 0){
return {...product, quantity:product.quantity-1};
}else{
return {...product};
}
});
}
incrementQuantity(){
console.log('increment')
this.productSignal.update((product) => (
{...product, quantity:product.quantity+1}
));
}
}
Points to note here-
- Product object is created as a signal.
- There are two methods to decrement or increment the quantity. In both of these methods signal value of the product is updated by decrementing or incrementing the product.quantity.
- There is a computed signal, totalPrice whose value is derived by multiplying the product.price and product.quantity.
singleproduct.component.html
<h1>Product List</h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ productSignal().id }}</td>
<td>{{ productSignal().name }}</td>
<td>{{ productSignal().price }}</td>
<td><button (click)="decrementQuantity()">-</button>{{ productSignal().quantity}}<button (click)="incrementQuantity()">+</button></td>
<td>{{totalPrice()}}</td>
</tr>
</tbody>
</table>
3. This example builds on the second example by having an array of Product objects and having a computed signal for grand total.
productsignal.component.ts
import { Component, computed, signal } from "@angular/core";
import { Product } from "../models/product.model";
@Component({
selector:'app-signalproduct',
templateUrl: './productsignal.component.html',
standalone:false
})
export class ProductSignalComponent{
products = signal<Product[]>([
new Product(1, 'Laptop', 1000, 1),
new Product(2, 'RAM', 30, 5),
new Product(3, 'Mouse', 15, 4),
new Product(4, 'Headphones', 200, 3),
]);
grandTotal = computed(() => {
return this.products().reduce((sum, product) => {
const productTotal = product.price * product.quantity;
return sum + productTotal;
}, 0);
});
decrementQuantity(index: number){
this.products.update(products => {
if(products[index].quantity > 0){
products[index].quantity--;
}
return [...products];
});
}
incrementQuantity(index: number){
this.products.update(products => {
products[index].quantity++;
return [...products];
});
}
}
Points to note here-
- Here we have an array of Product objects.
- For incrementing or decrementing quantity, index of the array is passed to the method as parameter. Using that quantity is incremented or decremented for that specific product.
- Since signals in Angular use reference equality to decide if something has changed. It is important to keep state of the object or array as immutable. That is why spread operator is used to create a new array object.
productsignal.component.html
<h1>Product List </h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
@for(product of products(); track product.id; let i = $index){
<tr>
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
<td><button (click)="decrementQuantity(i)">-</button>{{ product.quantity}}<button (click)="incrementQuantity(i)">+</button></td>
<td>{{ product.price * product.quantity}}</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td colspan="4"><strong>Grand Total:</strong></td>
<td><strong>{{ grandTotal() | currency:'USD' }}</strong></td>
</tr>
</tfoot>
</table>
That's all for this topic Signal in Angular With Examples. If you have any doubt or any suggestions to make please drop a comment. Thanks!
>>>Return to Angular Tutorial Page
Related Topics
You may also like-






