If you’re using Angular, you’ve probably heard about Zone.js. It’s an amazing library which does a lot of magic but feels somewhat unnecessary. The library’s only purpose is to monkey-patch events to detect all changes made inside your application and magically rerender the view.
For example, when you click a button or update a property in any component, Zone.js will detect that change and check the entire component tree starting from the parent component down to each component. While Zone.js is well-optimized and this process is really fast, Angular ends up recalculating and rechecking a lot of unnecessary code because we don’t really know where the change occurred. All we know is that a click event or update event has been triggered somewhere, and the view might need to be rerendered somewhere.
To optimize the change detection process, Angular introduced the ChangeDetectionStrategy.OnPush. This strategy checks only the component branch that was impacted by the change, down to the component where the change occurred.
✅ This approach saves unnecessary calculations and improves performance.
❌ However, using the OnPush strategy requires developers to have more knowledge of Angular in order to trigger change detection correctly. (manual markForCheck, async pipe, new instance of Input, …).
(Please note that this article does not focus on the workings of the OnPush strategy.)
A more optimized change detection mechanism is the reason why Angular needed a new system to detect changes more precisely. To accomplish this, Angular introduced a new primitive called “Signal”.
A Signal is a primitive that encapsulates an object, string, or other type of data. When the encapsulated variable is updated, the framework can precisely determine which View is impacted and needs to be refreshed.
As of the time of writing (Angular v16), the Signal component has not yet been introduced, and Signal change detection still relies on Zone.js. The following section is based only on the RFC.
If we simplify the concept of **View **to its component template, and update a signal listen to inside the template, we have the following schema:
When a signal is updated using the set , update or mutate method, any templates that listen to that signal are notified and the Views and only those Views are refreshed.
- Only Signals that are listened to inside a template trigger a new cycle of change detection. If a Signal is only called inside a function, no rerender will happen on the UI side.
- The entire template gets refreshed, not only the Signal binding. This means that all functions and properties will get reevaluated.
It’s a tradeoff that the Angular team has made. They decided to reduce the granularity of an application to its Views. Setting the dependency graph is expensive because it takes up memory. A View is already a small enough fragment of the UI to provide good rerender performance. (Remember that with Zone.js, Angular rerenders the entire application.)
A View is a piece of code encapsulated inside a ng-template. Every structural directive, such as
ngForcreates a new VIEW. So if your template has a large data table created with
ngFor, each row will have a separate view and only the row impacted by a signal change will be rerendered.
If we take our previous illustration and create two Views inside our component X, we will get the following:
👉 All of this will be working with and **without **Zone.js on signal based component in future version of Angular.
That’s it for this article! I hope you now have a better understanding of how signals will improve the performance of your Angular application in the near future. Get ready to start experimenting with signals, as the API is already available in Angular v16.
The future of Angular is looking very bright. 🚀