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!