Angular Signals: A Comprehensive Guide

Angular
βŒ› 7 minutes read

Introduction

Hello, developers! Today, we’re going to dive into a fascinating feature introduced in Angular 16 – Angular Signals. We’ll break down what Angular Signals are, how to use them, and even take a peek under the hood to see how they work.

Understanding Observer Design Pattern

Angular Signals is a new addition to the Angular framework. It is based on the Observer Design Pattern (ODP). First things first, let’s understand the ODP

The Observer Design Pattern is a software design pattern that establishes a one-to-many dependency between objects. It’s like a newsletter subscription model. Let’s break it down:

Publisher (Subject): This is like a magazine or newsletter publisher. It maintains a list of subscribers and sends updates when there’s new content. In the Observer Design Pattern, the Publisher is also known as the Subject.

Subscribers (Observers): These are like the readers who subscribe to the newsletter. They express interest in receiving updates, and when new content is published, they get notified. In the Observer Design Pattern, Subscribers are also known as Observers.

So, in the context of Angular Signals:

  • The Signal is the Publisher. It holds a value and a list of subscribers (functions or components that are interested in the value).
  •  The Components or Functions are the Subscribers. They subscribe to the signal and react whenever the signal’s value changes.

This pattern is particularly useful in event-driven programming and for instances where changes to one object (the Publisher) must be reflected in other objects (the Subscribers) without keeping the objects tightly coupled.

Project Setup

Before we dive into Angular Signals, let’s make sure we have an Angular project set up. If you’re new to Angular, don’t worry, We’ve got you covered. Here’s how you can create a new Angular project:

First, you need to install Node.js and npm (Node Package Manager) if you haven’t already. You can download them from here.

Once you have Node.js and npm installed, you can install Angular CLI (Command Line Interface), which is a powerful tool that helps us to initialize, develop, and maintain Angular applications. Run the following command in your terminal:

npm install -g @angular/cli

Now, let’s create a new Angular project. Navigate to the directory where you want to create your project and run:

ng new angular-signals-demo

This command creates a new directory named angular-signals-demo and sets up a new Angular project inside it. Navigate into your new project directory:

cd angular-signals-demo

Great! Now we’re ready to explore Angular Signals.

Technically speaking, Signals is a zero-argument function. When we call this function, it returns its current value. The cool thing about signals is that they can be observed for changes.

let mySignal = signal(0); // creates a signal with initial value 0
console.log(mySignal()); // logs the current value of the signal

In the code snippet above, we create a signal mySignal with an initial value of 0. When we call mySignal(), it returns its current value, which we then log to the console.

In the next section, we’ll dive deeper into how to create and use Angular Signals in your applications.

Started with Angular Signals

Now that we have our Angular project set up and we’ve understood the basics of Angular Signals, let’s dive into how we can create and use these signals in our application.

Creating a Signal

Creating a signal in Angular is as simple as calling the signal() function. This function takes an initial value and optional configuration options, and it returns a “WritableSignal“, which is a signal that can be updated.

Here’s how you can create a signal:

let count = signal(0); // creates a signal with initial value 0

In this example, we’re creating a signal named count with an initial value of 0.

Reading a Signal

Reading the value of a signal is as straightforward as calling the signal like a function:

console.log(count()); // logs the current value of the signal

Here, we’re logging the current value of the count signal to the console. Each time you call count(), it will return its current value.

Updating a Signal

To update the value of a signal, we use the writeSignal() function. This function takes a signal and a new value, and updates the signal with the new value:

writeSignal(count, 1); // updates the value of the signal

In this example, we’re updating the value of the count signal to 1. After this line, whenever you call count(), it will return 1.

Angular’s Representation of a Signal

In Angular, a signal is represented by an interface with a getter function and a SIGNAL symbol. The getter function returns the current value, and the SIGNAL symbol allows the framework to recognize it.

Here’s what the interface looks like:

interface Signal<T> {
  (): T;
  [SIGNAL]: unknown;
}

In this interface, (): T; represents the getter function that returns the current value of the signal, and [SIGNAL]: unknown; is the SIGNAL symbol that Angular uses to recognize signals.

That’s it for creating and using Angular Signals! In the next section, we’ll take a peek under the hood to see how Angular Signals work at a deeper level

Under the Hood: How Angular Signals Work

Now that we’ve seen how to create and use signals, let’s take a peek under the hood to understand how they work. Angular Signals are more than just a simple value holder – they’re a key part of Angular’s reactive programming model. Here’s what’s happening behind the scenes

Dependency Tracking

When a function (let’s call it a computation) reads the value of a signal, it becomes dependent on that signal. Angular keeps track of these dependencies and builds a graph. This graph is used to determine which computations need to be run when a signal’s value changes.

Here’s an example:

let count = signal(0); // creates a signal with initial value 0

let computation = () => {
  console.log(count()); // logs the current value of the signal
};

computation(); // run the computation

In this example, the computation function reads the value of the count signal. This means computation is dependent on the count, and Angular adds this dependency to the graph.

Change Propagation

When a signal’s value is updated, Angular looks at the dependency graph and runs all computations that are dependent on that signal. This is how changes in a signal’s value propagate through your application.

Here’s an example:

writeSignal(count, 1); // updates the value of the signal

In this example, when we update the value of the count signal, Angular will run all computations that are dependent on the count (in this case, the computation function).

Memoization and Change Detection

To avoid unnecessary work, Angular can remember (or “memoize”) the result of a computation and only re-run the computation if the values it depends on have changed. This is a powerful optimization that can make your application more efficient.

Let’s consider a simple example:

let count = signal(0); // creates a signal with initial value 0

let computation = memo(() => {
  console.log(count()); // logs the current value of the signal
});

computation(); // run the computation, logs "0"
computation(); // doesn't log anything because the count hasn't changed

In this example, we’re using a hypothetical memo() function to create a memoized computation. The first time we run computation(), it logs the value of the count. The second time we run computation(), it doesn’t log anything because the value of the count hasn’t changed.

Batched Updates

Sometimes, you might update a signal multiple times in quick succession. To avoid running computations unnecessarily, Angular can batch these updates together and treat them as a single update. This means computations will only run once, after the last update.

Here’s an example:

let count = signal(0); // creates a signal with initial value 0

let computation = () => {
  console.log(count()); // logs the current value of the signal
};

batch(() => {
  writeSignal(count, 1); // updates the value of the signal
  writeSignal(count, 2); // updates the value of the signal
  writeSignal(count, 3); // updates the value of the signal
});

computation(); // run the computation, logs "3"

In this example, we’re using a hypothetical batch() function to group multiple updates to the count signal. Inside the batch() function, we update the count three times. However, these updates are batched together, so when we run computation(), it only sees the final value of the count.

That’s a peek under the hood of Angular Signals! As you can see, signals are a powerful tool that can make your Angular code more efficient and easier to understand. In the next section, we’ll discuss the impact of Angular Signals on Angular development.

Impact of Angular Signals on Angular Development

The introduction of Angular Signals has brought about significant changes in how we develop with Angular. Let’s dive into some of the key areas that Angular Signals have influenced.

Data Flow

Angular Signals have introduced a more reactive data flow in Angular applications. With Signals, data changes are automatically propagated through your application, and only the components that depend on the changed data are updated. This makes it easier to reason about how data changes over time and how those changes propagate through your application.

Change Detection Mechanism

Angular’s change detection mechanism has been greatly enhanced with the introduction of Signals. Previously, Angular had to check every component for changes whenever any data changed, which could be inefficient for large applications. With Signals, Angular only needs to update the components that are directly dependent on the changed data. This can lead to significant performance improvements.

Component Lifecycle

Signals also have an impact on the component lifecycle in Angular. With Signals, components can react to changes in their inputs in a more fine-grained way. Instead of having to implement lifecycle hooks like ngOnChanges to react to input changes, components can simply observe their inputs for changes using Signals. This can make components simpler and easier to understand.

Use of Reactive Values

Finally, Angular Signals have made it easier to use reactive values in Angular applications. Reactive values are values that can change over time and can be observed for changes. With Signals, any value can be made reactive, and components can observe these values for changes and react accordingly. This makes it easier to integrate with reactive programming libraries like RxJS.

Further Reading

Conclusion

In conclusion, We talked about Angular Signals, exploring their creation, usage, inner workings, and the significant impact they have on Angular development. As we’ve seen, Angular Signals offer a more efficient, reactive, and intuitive approach to managing data flow in our applications. Whether you’re a beginner or an advanced developer, incorporating Angular Signals into your projects can undoubtedly elevate your Angular coding skills. So, go ahead, and explore it by yourself. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *