DEV Community

Soumaya Erradi
Soumaya Erradi

Posted on

Angular Signals: From Zero to Hero

Reactive programming has become a central paradigm in modern frontend development, with frameworks like Angular relying heavily on observables, event emitters and state management patterns. But with the introduction of Signals, Angular has unlocked a simpler, more intuitive way to manage local state reactively.
In this article, we’ll take you from the basics to advanced use cases of Signals in Angular, explaining how they work, why they’re different from observables and how you can use them to build high-performance Angular applications.

What are Angular Signals?

Angular Signals are reactive values that update automatically when their dependencies change. Think of them as reactive variables: they’re lightweight, synchronous and easy to work with, making them ideal for managing local component state. Unlike observables, which are typically used to handle streams of asynchronous data, signals are synchronous and work perfectly with Angular’s change detection.

In Angular, a signal represents a value that can change over time, and other signals can depend on it. This creates a natural flow of reactive state management within your application, where updates are propagated automatically.

Example:

import { signal } from '@angular/core';

// Define a signal
const count = signal(0);

// Update signal value
count.set(1);

// Read the signal value
console.log(count()); // Outputs: 1
Enter fullscreen mode Exit fullscreen mode

How Signals Differ from Observables

Although both observables and signals are reactive programming constructs, they differ significantly in their purpose and behavior.

Feature Signals Observables
Push vs Pull Pull-based (get when needed) Push-based (values are emitted)
Synchronous Synchronous and immediate Can be asynchronous
Subscriptions Not required Required
Use Case Local state Streams, async operations

While observables are ideal for streams of events (e.g., HTTP requests, WebSocket messages), signals shine when dealing with local, synchronous state that needs to be accessed and updated within your Angular components.

Setting Up Signals in Angular

To start using signals in Angular, make sure you are working with Angular 16 or above, as this is when they became fully integrated into Angular Core. Below is a basic example of how to use signals in an Angular component:

import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="increment()">Increment</button>
    <p>Count: {{ count() }}</p>
  `
})
export class CounterComponent {
  count = signal(0);

  increment() {
    this.count.set(this.count() + 1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Concepts: Derived and Computed Signals

Angular allows you to create derived signals (signals that depend on other signals) which automatically update when their dependencies change. This makes it easy to handle complex state dependencies.

Derived Signals

Derived signals are perfect for managing calculated values that depend on other signals.
Here’s a simple example of creating a derived signal:

const baseCount = signal(10);
const doubleCount = computed(() => baseCount() * 2);

console.log(doubleCount()); // Outputs: 20
Enter fullscreen mode Exit fullscreen mode

In this example, doubleCount will always return twice the value of baseCount. If baseCount changes, doubleCount will update automatically.

Computed Signals

You can also create computed signals, which act similarly to derived signals but are used when you need to perform more complex calculations.

const a = signal(5);
const b = signal(3);
const sum = computed(() => a() + b());

console.log(sum()); // Outputs: 8
Enter fullscreen mode Exit fullscreen mode

Whenever a or b changes, the sum signal will recalculate automatically.

Integrating Signals with Angular Change Detection

One of the key advantages of signals is their deep integration with Angular’s change detection mechanism. Signals trigger updates to the view automatically, even when using OnPush change detection. This significantly improves performance without the need for manual change detection calls.

Example with OnPush:

@Component({
  selector: 'app-signal-example',
  template: `<p>{{ fullName() }}</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SignalExampleComponent {
  firstName = signal('Soumaya');
  lastName = signal('Erradi');
  fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}
Enter fullscreen mode Exit fullscreen mode

Even with OnPush, the view will update whenever firstName or lastName changes because of the reactive nature of signals.

Signals in Complex Applications

For more complex scenarios, such as managing the state of an entire application, signals can simplify state management by providing clear and declarative dependencies between different parts of the state. For instance, in a shopping cart application, you can use signals to manage cart items and automatically calculate the total price based on the items added.

Example: Shopping Cart with Signals

@Injectable({ providedIn: 'root' })
export class CartService {
  private items = signal<CartItem[]>([]);

  addItem(item: CartItem) {
    this.items.set([...this.items(), item]);
  }

  getTotalPrice = computed(() =>
    this.items().reduce((total, item) => total + item.price, 0)
  );
}
Enter fullscreen mode Exit fullscreen mode

The getTotalPrice signal will automatically update whenever items are added to or removed from the cart.

Best Practices for Signals

When using signals in Angular applications, follow these best practices for optimal performance and maintainability:

  • Use signals for local, synchronous state: Signals are ideal for managing component state or any synchronous data dependencies within a component.
  • Keep derived signals simple: While derived signals are useful, keep your dependencies minimal to avoid overly complex signal chains that are difficult to debug.
  • Leverage automatic updates: Don’t overcomplicate your signal-based components by manually tracking state updates. Let Angular handle it for you.
  • Combine Signals and Observables: In some cases, you might still need observables for managing asynchronous data. In such cases, use signals for synchronous state and observables for streams.

Conclusion

Angular Signals introduce a fresh approach to managing reactive state in Angular applications. They offer a lightweight, synchronous and declarative way to manage state, making your applications easier to reason about and maintain. As you build out more complex applications, signals can reduce the need for boilerplate code, eliminate the need for manual subscriptions and streamline your change detection.

With this foundational knowledge, you're ready to start building more reactive and performant Angular applications using signals.

Happy coding!

Top comments (5)

Collapse
 
kishore_kch profile image
Kishore Andra • Edited

Easy to understand 😀, thanks for writing one 🙏

But the computed signals use computed method as in docs is it 🤔

const count: WritableSignal<number> = signal(0);
const doubleCount: Signal<number> = computed(() => count() * 2);

Collapse
 
soumayaerradi profile image
Soumaya Erradi • Edited

Using computed like this is key for creating signals that depend on other signals, allowing Angular to handle dependencies and updates automatically.

Collapse
 
kishore_kch profile image
Kishore Andra • Edited

Thanks, apologies but to me I feel that this post seems to be articulated by AI ... well it is perfectly alright but hopefully after reviewed by you or any human before 🚀😊

Collapse
 
maqsaid profile image
Maq Said

The step-by-step guide from basics to advanced use cases is incredibly helpful.

Collapse
 
haakontheadequate profile image
Haakon Hestness

Great article , I’ve been using signals for awhile now and they are awesome!