Imagine you’re in a busy café with a friend. Whenever your coffee order is ready, a light above the counter flashes—it’s a signal! You don’t need to keep asking if your coffee is ready; the light tells you instantly.
They act like messengers, notifying your application when data changes so everything updates seamlessly, without you needing to check constantly.
Signals are reactive state management mechanism that provide an intuitive way to manage and track state changes while improving performance by removing the need for manual subscriptions, zonal bindings, or complex state management libraries.
*Nice to know that signals are *
- Declarative State Management: Signals create observable values that automatically update when dependencies change.
- Fine-Grained Reactivity: Updates are localized, meaning only components or templates depending on the signal will re-render.
- Simplicity: Minimal boilerplate compared to other reactive paradigms like RxJS.
How do we use signals?
1.Create a signal with an initial value.
// import signal to use
import { signal } from "@angular/core";
// define a signal with intial value
const _counter = signal(0);
2.Read a signal value.
_counter()
3.To change the value.
_counter.set(1);
4.To change the value with make some login
_counter.update((value) => {
if(value == 0) return 0;
return value - 1;
});
5.Derived signals calculate values based on other signals; this will create a new signal based on the current signal.
const doubleCounter = computed(() => _counter() * 2);
6.To create a signal from observable.
const rxSignal = fromObservable(of(1, 2, 3));
7.To create an observabel from a signal.
const obs$ = toObservable(rxSignal);
obs$.subscribe(value => console.log(value));
- To define readonly signal, you can use computed signal to define a signal as readonly
// both are readonly
_counter: Signal<number> = signal(0);
_doubleCount: Signal<number> = computed(() => this._counter() * 2);
- To define writable signal, you can use default signal or WritableSignal
// both are writable
_counter = signal(0); // default is writable
_counter: WritableSignal<number> = signal(0);
Now let's go with a counter example:
It has two components (one for view and a second for increment/decrement) and one service to manage our signal.
1.Counter serive to manage our signal.
we have three function :
- *getCounter: * To read signal value.
- *increment: * To increment the signal value.
- *decrement: * To decrement the signal value and apply a condition.
import { signal } from "@angular/core";
export class CounterService {
private _counter = signal(0);
getCounter() {
return this._counter();
}
increment() {
this._counter.update((value) => value + 1);
}
decrement() {
this._counter.update((value) => {
if(value == 0) return 0;
return value - 1;
});
}
}
2.Control component that uses the counter service and increments/decrements the signal value.
export class CounterControlsComponent {
constructor(public counterService: CounterService){}
}
<button (click)="counterService.increment()">Increment</button>
<button (click)="counterService.decrement()">Decrement</button>
- View component that uses the counter service to read and display a signal value.
export class CounterViewComponent {
constructor(public counterService: CounterService){}
}
<p>Counter : {{counterService.getCounter()}}</p>
you should see the view like this, and when you click on decrement when the value is 0 , it shouldn't make anything, just display 0
Summary
- Use signals for localized and component-level state.
- Prefer computed signals for derived state instead of duplicating logic.
- Combine with RxJS when dealing with streams or external observables.
Top comments (0)