Clear examples of change detection on different application triggers. Click, Input + NgModel, AsyncPipe, Signal, Web API — setInterval
After years of waiting and practicing “how to tame the dragon” skills, the Angular team has finally released an update that allows you to test building applications without using ZoneJs
In this article I would like to show how change detection is performed across the entire component tree when using different triggers, namely:
Click event — button click event + handler
Input
+NgModel
— processing of user input with saving to a variable via two-way data bindingAsyncPipe
— output value into a template fromObservable
Signal
— output value into a template fromtoSignal(obs$)
setInterval
— Web API
These triggers will be tested in a few environments:
NgZone
+ChangeDetection.Default
NgZone
+ChangeDetection.OnPush
Zoneless
+ChangeDetection.Default
Zoneless
+ChangeDetection.OnPush
Environment
Angular 18.0.7
zone.js 0.14.3
Rxjs 7.8.0
Click event
Let’s start with a simple one. Button events are one of the most frequent events of any application. They are used for web page interactivity — navigation, form validation, animation triggering, etc.
NgZone + Default
NgZone + OnPush
Zoneless + Default
Zoneless + OnPush
Summary
From these tests, we can see that the change detection behavior between NgZone
and Zoneless
is not different, and to reduce the number of checks it is necessary to use a detection strategy via OnPush
Input + NgModel
Another one of the popular elements of any web application — search, login form, filtering data in a table. Listening and validating element values can be done in different ways from addEventListener
to SubmitEvent
, but nevertheless you should always listen. For these examples we use the ngModel
binding
NgZone + Default
NgZone + OnPush
Zoneless + Default
Zoneless + OnPush
Summary
Similar to the button and click event tests, there is no difference between NgZone
and Zoneless
and OnPush
should be used to get a better and more optimized version of the application
AsyncPipe
One of the older Angular APIs for handling asynchronous events and template updates. RxJS
and AsyncPipe
make it easier to handle asynchronous data flows, make code cleaner and more reactive, and improve the manageability of Angular applications.
From a component update perspective, there is not much difference between subscribing inside a component with a call to markForCheck()
and using AsyncPipe
. We have a new value — we need to request an update.
For this example we will use a simple timer()
counter with a period of 1 sec and display the value on the screen.
NgZone + Default
NgZone + OnPush
Zoneless + Default
Zoneless + OnPush
Summary
Again no change between Zoneless
and NgZone
, still need to use OnPush
to reduce the number of component tree checks
Signal
A new API for handling asynchronous values on a pub/sub basis.
Angular Signals is a system that keeps detailed track of how and where your state is used in the application, allowing the framework to optimize rendering updates.
The familiar timer()
function will be used as the data source, with conversion to the Signal API via the toSignal()
function
NgZone + Default
NgZone + OnPush
Zoneless + Default
Zoneless + OnPush
Summary
Finally we can evaluate and see the impact of signals on change detection checking for the whole application.
The event detection cycle for the entire component tree only works in the NgZone
+ Default
mapping. In all other cases, changes coming from signal are isolated for the component and do not require checking even the component itself. Wow 🔥
Web API — setInterval
The basic idea behind zone.js
is to create an execution context, called a “zone”, that can intercept and handle all asynchronous operations occurring in that context. Zones can intercept and handle all asynchronous operations occurring in them. This is accomplished by overriding standard functions such as setTimeout
, setInterval
, promises
, etc.
In simple language, “overriding” a function is monkey-patching. This is how zone.js
notifies Angular to check for a change.
setInterval
is one of the global functions available when code is executed in the browser. It is considered good practice to wrap the setInterval
call in zone.runOutsideZone()
to avoid unnecessary validation loops
NgZone + Default
NgZone + OnPush
Zoneless + Default
Zoneless + OnPush
Summary
The tests give an unexpected picture, the use of setInterval
has a significant impact on component tree checking only when using the NgZone
+ Default
mapping. In other cases, using setInterval
is safe without using runOutsideZone
, and does not affect component tree checking.
Conclusion
It is relatively safe to use Zoneless in current applications when upgrading to version 18. All necessary component checks and trigger responses are similar between Zone.js and Zoneless. If possible, you should always follow the event detection strategy via OnPush
.
Signal’s
were a pleasant surprise. Template updates are isolated and do not affect the component tree, which will be extremely useful for partial hydration.
There is no need to be afraid of using asynchronous global Web API. Even when using Zoneless
+ Default
— it doesn’t cause component tree checking and all calculations happen in the background.
I realize I have only checked a small part of possible scenarios and triggers, there are many more examples — using Signal-input
/ output
/ model
, Signal Store
, etc.
Bonus
One of the benefits of dropping ZoneJs
will be to lighten the current build.
If your standard application with 0 logic at the start is 216kb
in size, ZoneJs
takes up 15% of the total build, which is quite a large share
After uninstalling ZoneJs
got rid of 33kb
and made your build lighter, and the app continued to work as before.
Top comments (3)
Hi Maksim Dolgih,
Top, very nice and helpful !
Thanks for sharing.
Have you noticed any practical challenges when transitioning existing projects to use these new features? 🤔 Also, how does this impact the overall developer experience in terms of debugging and maintenance?
I haven't noticed yet. But it is worth noting that projects have always followed "best practices". I haven't had any places using the Zone API, which is removed and confirmed by the Angular team
angular.dev/guide/experimental/zon...
Also the code that used runOutsideAngular works without problems, although it is redundant in Zoneless