跳转到主要内容
Why and How Using Angular Signals I have updated this article after the release of Angular v16.0.0-next.7 Before we begin with Angular Signals 🚦, have you ever wondered how changes made in your model reflect in the view ? If you know how Angular detects changes and updates views, you can proceed to the next section about signals! 💨 When you make any updates to your model, Angular automatically applies those changes to the corresponding view. This enables a seamless and synchronized interaction between model and view, allowing for a smooth user experience. Believe me, it’s not by magic! 🧙‍♂️✨ The key to this synchronization is Change Detection and Zone js. Although CD and Zone js won’t be the focus of this article, we’ll briefly explore how Angular uses these tools to synchronize the view with models. Angular applications are built using a component-based architecture, where each component represents a specific feature or functionality. Angular application is a tree of components. By using zones and executing code within them, the framework can gain better visibility into the operations being performed. This enables it to handle errors more effectively, and most importantly, trigger change detection whenever somthing happens into the application (Asynchronous operation, DOM Event …) 1 : Zone.js detects when something happens in the application. 2 : Zone.js triggers change detection to update the application state. When change detection starts 🔥, the framework goes through all the components in the tree to verify if their state has changed or not and if the new state affects the view. If that’s the case, the DOM part of the component which is affected by the change is updated. This functionality have a significant impact on the performance of the application, it does work that may not be necessary, since the majority of components may not be affected by the event. We can optimize the application’s performance by changing the change detection strategy in component by using the OnPush strategy. However, this approach requires a careful attention. But why are you explaining this? This article is about Angular Signals, right? 😕 Yes, because Angular Signals can help us avoid this unnecessary checks😊 and update only the part of the application that has changed. So now we will learn about Angular Signals 🥳🥳🥳. Let’s go 🏃‍♀️ 🏃 🏃‍♀️ 🏃 Angular 🚦 Signals 📡 What are Angular Signals 🤔? Signals are a reactive value, technically are a zero-argument function [(() => T)] , when we execute it they return the value. We can say that signal is a special type of value that can be observed 🧐 for changes. How do we create a signal ? We create and initialize a signal by calling the function signal() // Signature of signal function export function signal(initialValue: T, options?: CreateSignalOptions): WritableSignal //Signature of CreateSignalOptions export interface CreateSignalOptions { /** * A comparison function which defines equality for signal values. */ equal?: ValueEqualityFn; } //How use it const movies = signal([]); The signal function is a TypeScript function that creates a Signal. It takes two arguments: initialValue : Represent the initial value of the signal, and it can be of any type T options is an object of the CreateSignalOptions type that includes an equal method for comparing two values of type T. If the options object is not provided when creating a signal, the defaultEquals function will be used instead. The defaultEquals function compares two values of the same type T using a combination of the === operator and the Object.is method The signal function returns a WritableSignal.Signal are a getter function, but the type WritableSignal give as the possibilty to modifiy the value by three methods : set [set(value: T): void] for replacement (set the signal to a new value, and notify any dependents) update[update(updateFn: (value: T) => T)] for deriving a new value (Update the value of the signal based on its current value, and notify any dependents), The update operation uses the set() operation for performing updates behind the scenes. mutate [mutate(mutatorFn: (value: T) => void)] for performing internal mutation of the current value (Update the current value by mutating it in-place, and notify any dependents) So Signal is a reactive value that can be observed, updated and notifiy any dependents. But what does it mean to notify any dependents😕 ? This is the magic 🪄 part of Signal. Now we will see what dependents are for Signals and how to notify them. Signal is not just a value that can be modified, it is more than that, Signal is a reactive value 🔃 and is a producer that notify consumers(dependents) when it changes. So dependents in Signal are any code that has registered an interest in the Signal’s value and wishes to be notified whenever the Signal’s value changes. When the Signal’s value is modified, the Signal will notify all of its dependents, allowing them to react to the change in the Signal’s value. This makes the Signal is the core element of a reactive application, as it allows different parts of the application to automatically update in response to changes in data. So, how we can add dependents (consumer) to Signal ? We can add consumers by using effect and computed functions. effect Sometimes, when a signal has a new value, we may need to add a side effect. To accomplish this, we can use the effect() function. Effect schedules and runs a side-effectful function inside a reactive context. Signature of the effect function : export function effect( effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef How use effect The function inside the effect will re-evaluate with any change that occurs in the signals called inside it. Multiple signals can be added to the effect function. 🔍 I will try to explain the work behind the effect function : 🔍 when we declare an effect function, the effectFn passed as an argument will be added to the list of the consumers of any signals used by the effectFn, such as movies in our example. (Signals used by the effectFn will be the producers). Then, when the signal has a new value by using the set, update, or mutate operators, the effectFn will be re-evaluated with the new signal value(The producer notifies all consumers of the new values). The effect() function returns an EffectRef, which is a global reactive effect that can be manually destroyed. An EffectRef has a destroy operation. destroy(): 🧹 Shut down the effect, removing it from any upcoming scheduled executions. computed What if there is another value that depends on the values of other signals, and needs to be recalculated 🔄 whenever any of those dependencies changes? In this case, we can use a computed() function to create a new signal that automatically updates whenever its dependencies change. computed()creates a memoizing signal, which calculates its value from the values of some number of input signals. Signature of the computed function : export function computed( computation: () => T, equal: ValueEqualityFn = defaultEquals): Signal How use computed So computed function will return another Signal, any signals used by computation will be tracked as dependencies, and the value of the new signal recalculated whenever any of those dependencies changes. Note that the computed function returns a Signal and not a WritableSignal, which means it cannot be manually modified using methods such as set, update, or mutate. Instead, it is updated automatically whenever one of its dependent parents signals changes. and now we can add and effect or create another signal with computed function based of this new Signal. Any change in values now will be propagated in the dependencies graph 🌳. That’s it 🤩! In this article, we’ve learned about Angular Signals, what they are, why we need them, and how to use them in our applications. I hope this has helped you understand the power of signals. To illustrate these concepts, I created a project 🎯 that explains Angular signals and demonstrates how to create and update signals, use effects, and create computed values. In the following GIF, you can see the application in action, and you can find the full code in this GitHub repository. If you find the code helpful, please feel free to give it a star⭐️. Thanks for reading, don’t forget to support this article with your 👏 to help share it with more people. And be sure to follow me in twitter or medium for more exciting articles in the future! 🔥