DEV Community

Eslam Heikal
Eslam Heikal

Posted on

Angular Signals

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);
Enter fullscreen mode Exit fullscreen mode

2.Read a signal value.

_counter()
Enter fullscreen mode Exit fullscreen mode

3.To change the value.

_counter.set(1);
Enter fullscreen mode Exit fullscreen mode

4.To change the value with make some login

_counter.update((value) => {
   if(value == 0) return 0;

   return value - 1;
});
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

6.To create a signal from observable.

const rxSignal = fromObservable(of(1, 2, 3)); 
Enter fullscreen mode Exit fullscreen mode

7.To create an observabel from a signal.

const obs$ = toObservable(rxSignal);
obs$.subscribe(value => console.log(value));
Enter fullscreen mode Exit fullscreen mode
  1. 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);
Enter fullscreen mode Exit fullscreen mode
  1. 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);
Enter fullscreen mode Exit fullscreen mode

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;
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

2.Control component that uses the counter service and increments/decrements the signal value.

export class CounterControlsComponent {
  constructor(public counterService: CounterService){}
}
Enter fullscreen mode Exit fullscreen mode
<button (click)="counterService.increment()">Increment</button>
<button (click)="counterService.decrement()">Decrement</button>
Enter fullscreen mode Exit fullscreen mode
  1. View component that uses the counter service to read and display a signal value.
export class CounterViewComponent {
  constructor(public counterService: CounterService){}
}
Enter fullscreen mode Exit fullscreen mode
<p>Counter : {{counterService.getCounter()}}</p>
Enter fullscreen mode Exit fullscreen mode

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

Image description

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)