Replies: 16 comments 95 replies
-
Unless I missed something, there are two points that expected to see discussed here and that I didn't find:
|
Beta Was this translation helpful? Give feedback.
-
I don't want to sound negative, but that means at some point in the future we will be forced to migrate our huge applications built on the old model. So should we start the migration early? What timeframe should we expect under the term foreseeable? |
Beta Was this translation helpful? Give feedback.
-
One question that I am not sure I understood is in the code you showed: <user-profile [userData]="someRegularVariable" /> You said it will be used as What can be done is check whether the provided input is signal based and then use computed or otherwise a new signal. But I am pretty sure that's impossible the same as automatically unwrapping is impossible |
Beta Was this translation helpful? Give feedback.
-
Perhaps I haven't read the overview too well, but I'm seeking some clarification around the following piece:
Just to ensure my understanding, this following piece will automatically create a computed signal for <user-profile [userData]="authService.loggedInUser().data" /> If @Component({
...,
template: `
<button (click)="logout()">Logout</button>
`
})
export class UsrPrfCmp {
@Input() userData: UserData;
private userService = inject(UserService);
logout() {
this.userService.logout(this.userData().id);
}
} Because Will Essentially, my questions here are: These questions are how I believe this to be working, but just seeking the clarification around it. |
Beta Was this translation helpful? Give feedback.
-
Could we rename |
Beta Was this translation helpful? Give feedback.
-
One thing that probably will get very beneficial is the ability to serialize the reactive graph of signals. This could potentially enable huge benefits when moving into a future where something like react server component (for angular) and/or qwik and/or astro and/or bling would render some things on the server and other things both on server and client. A serializable reactive primitive would enable really crazy usecases => RSC on steroids. Is there any consideration about the serializability of the reactive primitive? Or other considerations to be able to get resumability (on client and also even on server)? This will enable a resumability in angular that could even surpass the resumability of qwik. |
Beta Was this translation helpful? Give feedback.
-
There is a use case we have using a custom made @Component({
selector: 'app-test',
template: `
Multiplier: {{ multiplier }}
<button (click)="incMultiplier()">INC</button>
<div *ngFor="let value of values; let index = index">
<div>Value: {{ value }}</div>
<button (click)="incValue(index)">INC</button>
<div>{{ isEnoughMsg | memoize : value : multiplier }}</div>
</div>
`,
})
export class TestComponent {
multiplier = 8;
values: number[] = []; // Some real array
incMultiplier() {
this.multiplier++;
}
incValue(index: number) {
this.values = [...this.values];
this.values[index]++;
}
// Can be any computation that I don't want to run too many times and can't be created with simple ngIfs in template
isEnoughMsg(value: number, multiplier: number) {
if (multiplier < 8) {
return '...';
}
return value * multiplier > 1000 ? '...' : '...';
}
} The main "issue" here is the |
Beta Was this translation helpful? Give feedback.
-
I seriously don’t understand why don’t you adopt “mobx”? Feels like you are trying to reinvent the wheel here… |
Beta Was this translation helpful? Give feedback.
-
I am not that sure about it any more but when signals where introduced for angular I thought that the solution should be as independent from angular as possible. I think the approach that ngrx is/was trying to establish to go zoneless with @ngrx/component is really interesting and some parts of this mental model should probably be considered much more when thinking about how to implement fine grained reactivity with signals right now. Fine grained reactivity could be also useful for @ngrx/component and even pure rxjs approaches. At the moment I am much more on the side that it fits angular to have one opinionated approach for this problem and getting even more opinionated with signals. But I can not get rid of the feeling that we miss some good stuff if we make the approaches of @ngrx/component and part of rx angular obsolete. Or am I missing something here? Will @ngrx/component and similar approaches still be relevant or are signals making all of these approaches completely obsolete? One thing that I really like about rx angular and to some extent also @ngrx/component is the possibility to have control over the scheduling. I think it would be really great to have the same or even more control over the scheduling with signals. |
Beta Was this translation helpful? Give feedback.
-
I wonder whether the subject of this RFC is honestly about the introduction of signals. "current proposal" appears to be built on something else entirely rather than just on the basis of adding signals, and I believe the line between what could have been done and what was done has been blurred intentionally. Ultimately it looks like we are heading towards a new rewrite of Angular and I wonder if it's proper that this results in killing what Angular is today. There was talk about the ease of adoption, and it seems to me that this is the underlying motivation for introducing these change (alignment to the current framework landscape). I am curious to see how a third rewrite will provide a positive impact on adoption and how many of us might second guess betting the future of a project on angular. |
Beta Was this translation helpful? Give feedback.
-
The logic behind the RxJS "glitch" is fundamentally broken, and it does not highlight the actual "glitch" in any way. |
Beta Was this translation helpful? Give feedback.
-
I really like this RFC. Angular's change detection was one of my major worries when we switched from Ember. Your concept reminds me a lot of knockout.js back in the day. One question came to mind: |
Beta Was this translation helpful? Give feedback.
-
@angular-robot Great work team! @alxhub Great point on migration of large scale apps at google. To give it an another dimension from an eagle eye view this is what I think why Signal should be introduced at earliest. We already had OnPush / RxJx strategy to resolve the performance issue (but we use it as per requirement, we are not forced to use it) up to certain levels but Signal(*) is very much familiar with the React hooks and Computed functions in Vuejs. It will allow not only folks to get an entry Angular world due to more familiarity of concepts but reduces ZoneJs as well as OnPush change detection learning curve for the community. I have already played around with it and really liked what Angular team has done and achieved :) |
Beta Was this translation helpful? Give feedback.
-
I would like to point out that I already found (educational) examples of misused My point is: This concept brings a lot of confusion and contrary to the standalone component API, it's not simplifying the learning curve of Angular (Zone.js just did its magic in the background and for most applications, especially beginner applications there was no downside to it). So I would like to bring up the following suggestions.
|
Beta Was this translation helpful? Give feedback.
-
Even though the set method for signals does not trigger change detection, it is still necessary to use |
Beta Was this translation helpful? Give feedback.
-
ImmutabilityTo ensure one-directional data flow, using immutability is very important to ensure best practices. Has this been considered for signals? Proposal: export type Signal<T> = (() => Readonly<T>)&{
[SIGNAL]: unknown;
};
export interface WritableSignal<T> extends Signal<T> {
set(value: T): void;
update(updateFn: (value: Readonly<T>) => T): void;
mutate(mutatorFn: (value: T) => void): void;
asReadonly(): Signal<Readonly<T>>;
} What could be protected:
Additionally, as NgRx does, it could provide additional runtime checks for dev mode like freezing: const names = new Signal([], { freeze: true }) Reference: ngrx/platform#3086 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Sub-RFC 1: Signals for Angular Reactivity
This discussion covers the choice of Signals as a new reactive primitive for Angular, and discusses several alternatives considered.
Why a new reactive primitive?
In a model-driven web application, one of the main jobs of the framework is synchronizing changes to the application's data model and the UI. We refer to this mechanism as reactivity, and every modern web framework has its own reactivity system.
Today, Angular's reactivity relies on the
zone.js
library. Reactivity withzone.js
has both unique benefits and unique challenges, which are discussed in more detail below (see: Why not continue with zone.js?). Crucially,zone.js
does not provide "fine-grained" information about changes in the model. Zone.js is only capable of notifying us when something might have happened in the application, and can give no information about what happened or what has changed.In order to achieve our main goals (see: top-level RFC goals) we'll need to introduce a new reactivity model to Angular, with capabilities beyond what can be achieved with
zone.js
. This means adding a new primitive to the framework which can provide fine-grained information about model changes. A major design goal of Angular Signals is to make the integration of these two reactivity models as seamless and straightforward as possible, so developers can concentrate on shipping features instead of reasoning about how change detection is going to work in their applications.Requirements for a new reactivity primitive
Before deciding on a new reactivity approach, we set out some requirements - properties that we would like the new reactivity solution to have:
Discussion point 1A: are there other requirements we should consider?
Options considered
We experimented with a number of different alternatives, including:
setState
-style APIsChoice of signal-based reactivity
During our research, one reactivity model stood out as clearly meeting our requirements while offering a very natural developer experience: signals. Signals are not a new idea in the framework space - Preact, Solid, and Vue all employ some version of this concept to great success.
So what is this primitive?
Signals
A signal is a wrapper around a value, which is capable of notifying interested consumers when that value changes. There are different kinds of signals.
Some signals can have their value directly updated via a mutation API. In our implementation, these are known as "writable" signals. Updates to the model are always done by changing one or more writable signals.
Because reading a signal is done through a getter rather than accessing a plain variable or value, signals are able to keep track of where they're being read. And because mutations are always done with the mutation API, signals can know when they're mutated and inform previous readers about the change.
Computed
A computed signal has a value which cannot be changed directly, but which is derived from the values of other signals.
Example of Signal and Computed
A good example of these two signal flavors working together is a
counter
numeric value and a booleanisEven
flag which tracks whether the counter is even. We could model thecounter
as a writable signal and increment or decrement it directly.isEven
is best modeled as a computed signal, since its value is determined by the value of thecounter
and should not be updated directly.Effects
Signals are useful because they can notify interested consumers when they change. An operation that's performed when the signals it depends on are changed is called an "effect". For example, Angular will use an effect to update a component's UI whenever any signals read within that component's template are changed.
Automatic Dependency Tracking
A cornerstone of our signal system design is that when computed signals and effects run, they keep track of which signals were read as part of the computation or effect function. Knowing the dependencies allows the signal system to re-run the computation or effect function automatically whenever any signal dependencies change.
Why is this a good fit for Angular?
Signals as a reactive primitive meet all of the above requirements:
Additionally, we see a number of additional benefits of signals:
Of all the alternatives explored (see below), our conclusion is that signals are the best fit for Angular and will allow us to meet (and in some cases exceed) our overall goals.
Integration with Signals
So how will Angular make use of this new reactive primitive?
Signals will affect many areas of the framework, especially including:
@Input
s)In this section, we'll dig into how signals will fit into Angular's component model.
Zones are here for the foreseeable future
It's important to recognize that we won't be able to replace zone.js with signals transparently under the hood. Zones and signals are based on fundamentally different assumptions about how data flows through an application, and a component written with zone.js in mind will likely not function without it. We may be able to provide some assistance in migration through tooling, but it will not be completely automatic.
That means for most applications, the new signal-based reactivity will need to coexist and interoperate with existing zone-based reactivity for the foreseeable future.
Signal-based components
We're introducing a new type of signal-based component, which uses signals for its reactivity and rendering. Both the new signal component and existing zone-based components will be able to use and interact with each other in the same application.
Using components as the boundary between reactivity systems will allow application developers to gradually opt-in to signal reactivity in an existing application, and allow library authors to convert their libraries to use signals in a backwards-compatible way.
Note: while this section is written mainly talking about components, almost all of the topics here apply equally to directives.
Local change detection
One significant new capability that signal-based components will have is local change detection. Unlike zone.js, signals give fine-grained information about which parts of the model have changed, signal-based components do not participate in the global change detection. Instead, Angular understands which signals are used in different parts of the component's template, and only synchronizes that component with the DOM when a signal changes.
This is the golden rule of signal components: change detection for a component will be scheduled when and only when a signal read in the template notifies Angular that it has changed.
In fact, in our current design this change detection will happen independently for each view within a component.
Aside: Angular's "View" concept
"Views" in Angular are static fragments of templates - sets of known UI elements, directives, and child components. Views are composed together to create templates that can express conditional or repeated sections of UIs.
For example, the following component template is a single view:
Whereas this template has two views - the outer DOM with "Who" and "What", and an embedded view which is conditionally shown which contains "Why":
Each branch of an
NgIf
orNgSwitchCase
and each row of anNgFor
are examples of independent views in Angular.Granularity of local change detection
The choice of per-view change detection is an important one, as there's an entire spectrum of granularity with which we could track changes to the model and update the DOM:
OnPush
optimizations).So why check per-view and not update individual bindings? Would that not be faster/more efficient? The tradeoffs here are not so clear-cut, and while we are interested in exploring designs with more granular updates in the future, there are a few reasons why we believe per-view checking is the best option for Angular's signals today.
Setting up the dependency graph is not free. Our signal library is fast, but the more granular the tracking, the more nodes in the graph need to be allocated and bookkept. This not only takes time, but consumes memory. We have to weigh this cost against the potential benefits of increased granularity.
Views are typically small fragments of UI with a manageable number of bindings. The cost to evaluate and change detect a small set of bindings is already very low (remember, Angular does this today for entire applications).
The most expensive components to process with change detection are often those with large, repeated, highly dynamic structures, such as data tables with hundreds or thousands of rows. Such components are naturally already broken down into many individual views (remember, each row is at least one view, maybe more). With view-based change detection and a properly structured model, signals can already be used to drive updates only to one row of the table without needing to process other unchanged rows.
The operation of running change detection against a view is already the main primitive on which the existing zone-based whole-application change detection system is built. Using the same granularity for signals makes the interoperability story much more straightforward, and allows the two reactivity models to share most of their underlying implementation code. This will allow us to introduce signal reactivity without greatly impacting bundle size.
@inputs are signals
In signal-based components, inputs will be signals! This choice is directly aimed at the goal of having a clear, unified model for how data flows through an application.
Signal-based inputs have a major impact on data flow, because they work as computed signals, and not change-detected expressions.
Aside: Inputs in Angular Today
To understand the difference in how signal-based inputs work, it's useful to consider in more detail how inputs work in zone-based applications today.
In a zone-based application today, inputs are set during change detection. Let's say the
HomePageCmp
has the following template:Suppose the
loggedInUser
changes. zone.js will notice something happened but doesn't know what specifically changed, and will trigger change detection for the whole application. Change detection will process theHomePageCmp
and re-evaluate the binding to[userData]
: the expressionauthService.loggedInUser.data
. It will set theUserProfileCmp.userData
field to the new value, before descending into theUserProfileCmp
and evaluating its template, which might make use of theuserData
.Signal-based inputs as computations
If
HomePageCmp
andUserProfileCmp
were signal based components, theuserData
input would function very differently. Of course, the binding would have to use a signal for theloggedInUser
:When
HomePageCmp
is first created, and is creating itsUserProfileCmp
child, Angular will create a computed signal for the[userData]
binding expression:computed(() => authService.loggedInUser().data)
. This derived signal is then provided as the value for theUserProfileCmp
'suserData
input.This has huge implications:
Signal inputs resolve before change detection
Updates to the
userData
input signal happen before change detection, not during.This is the major difference from zone-based component inputs. With signals, the "model to model synchronization" of propagating changes to
userData
resolves before Angular starts any UI synchronization.This eliminates an entire category of data flow challenges faced by zone-based applications:
ExpressionChangedAfterItHasBeenChecked
errors are no longer a risk, because the model is fully synchronized before it gets checked. (A non-deterministic model can still cause such errors, but that's a different problem)setTimeout
orPromise.resolve
or other tricks.Input bindings don't trigger local change detection in the binder
Because
userData
's binding is provided as a computed signal, none of the views in theHomePageCmp
depend on its value. Per the golden rule of signal-based change detection, none of its views will be change detected.Input bindings don't automatically trigger local change detection in the receiver
Just because
UserProfileCmp
receivesuserData
as a computed signal does not mean that any of its views will be change detected whenuserData
changes. Per the golden rule,UserProfileCmp
will only be change detected if it reads theuserData
input signal somewhere in its template.In other words, inputs which aren't actually used in templates don't trigger any change detection in signal-based components.
loggedInUser
changes,HomePageCmp
does not need to change detect any views.That's because it never actually reads the
loggedInUser()
value for itself, it only forwards it as a computation to its child component.Queries are signals
Like inputs, view and child queries are an example of the framework "producing" a reactive value which represents some aspect of the component model. In signal-based components, queries will also be exposed as signals. This allows components to naturally react to queries changing via computed properties or effects, just like with inputs.
Mixing Signal and Zone Components
It's possible to freely mix signal and non-signal components in the same application. As long as the golden rule of signal component is observed, change detection should function correctly even across these boundaries.
When a signal component provides an input binding to a non-signal component, signal semantics are used to detect when the binding changes, and set the input of the non-signal component, mark it for check if necessary, and run
ngOnChanges
if needed as well.When a non-signal component binds to the input of a signal component, a similar conversion happens. During change detection of the non-signal component, the binding is evaluated and, if the value has changed, the input signal passed to the child component is updated.
Caveat: non-signal API issues
Sometimes directives expose APIs for their consumers, via local references or the DI system. For example,
NgModel
exposes its currentvalue
as a public property, accessible via local refs:Such APIs may be problematic in signal components, since
in.value
isn't a signal and can't be used to trigger change detection for signal components, even though it changes as the user types.Note that in zone-based components though, relying on data from a child like this is hugely problematic anyways. If the reflection of the current value is moved above the
<input>
declaration, anExpressionChangedAfterItHasBeenChecked
error will result.For Angular-authored directives, we will revisit their APIs and adapt them as needed to work with signal-based change detection. We would expect community libraries to eventually do the same, but this process will take time.
Zoneless Angular
With signals, it will be possible to build an Angular application without zone.js. Rather than relying on zones for change detection to function, signal-only applications will schedule change detection of individual views directly (likely via
requestAnimationFrame
or some other browser primitive).In such an application, it would be an error to attempt to use a zone-based component.
Frequently Asked Questions
Architecture
Will signals lead to zone.js being optional?
Yes, this is one of the driving motivations that led the team to signals.
If you write an application exclusively composed of signal-based components, you will be able to remove the dependency on zone.js.
Is zone-based change detection deprecated or removed?
No, zone-based change detection will remain supported for the foreseeable future. Components that depend on zone.js and/or signals can be freely mixed in one application. An application would have to fully track its model in signals to completely remove dependency on zone.js.
Are we going to have another change detection strategy?
Many applications and teams recommend the
OnPush
change detection strategy to improve performance. Signal-based components have "OnPush
on steroids" baked-in.In the design proposed here only components that read an updated signal will have to be change-detected. None of its parents and none of its children. One and only one component that needs to display changed data.
Signal based components have all the benefits of the
OnPush
strategy (and more!) without any of its drawbacks - by default. In this sense we actually remove the very concept of change detection strategy..What about state management with signals?
Signals are a great primitive for managing state and data flow, but we fully expect that more sophisticated stores and state management solutions will emerge. Dedicated state management libraries can address many development concerns, such as granularity of data, collocating signals with custom update methods, managing effects, composing "stores" etc.
We have no plans to develop a first-party state management solution in Angular itself. The broader Angular community, though, possesses a wealth of knowledge and experience in this area. Some major state management libraries have already started to explore incorporating signals:
Why not use RxJS
Observable
as the reactive primitive?Observables are a very powerful and flexible primitive for expressing computation in a functional and reactive way. They're used throughout the Angular ecosystem already, often to achieve more fine-grained reactivity than zone.js provides out of the box. For this reason, Observables do seem at first glance to be a natural fit for a new reactivity system for Angular.
We did extensively explore this option, and our conclusion is that while Observables are a powerful primitive, they are a poor fit for template rendering specifically.
Observables are naturally asynchronous
Observables can be used to model both synchronous and asynchronous data flow. However, they don't distinguish these two cases in their API - any
Observable
might be synchronous, or it might be asynchronous. This duality makes them flexible, but creates issues when using them to model template reactivity.In an Angular template, bindings always have a current value. For example the property binding:
always provides a value to display inside the
<input>
. Angular doesn't need to consider the case where the[value]
binding doesn't have a value to set yet. If this binding were driven by an Observable instead (let's sayname$
), then we would lose this guarantee: the Observable may not provide a value synchronously on subscription. In fact it may never produce a value. Angular would therefore need to deal with any binding potentially being in this "pending" state.This is partly the problem that the
async
pipe solves. Theasync
pipe always has a value, even if the Observable it's subscribed to has not yet produced a value. Until it does, theasync
pipe will returnnull
as its value. This behavior is known to be somewhat annoying (requiring patterns like*ngIf="data$ | async as data"
to guard against thenull
s) and would become even more so if every binding behaved that way, if every input were also forced to benull
able, etc. Andnull
is not the right choice as a "default" for all bindings (text bindings for example would probably want to show an empty string).We could instead enforce that every Observable used in a template must be synchronous, but this is also an arguably poor experience. Since there is no way to enforce synchronicity through typings, it would need to be enforced with a runtime exception. This could lead to subtle issues if asynchronicity is accidentally introduced deep within an Observable chain, especially if it's only conditionally asynchronous.
Observables are not side-effect free
Another observation (pun intended) is that Observables are often cold and side-effectful. That is, every subscription may potentially trigger arbitrary behavior, up to and including RPCs to backend APIs. This is how
HttpClient
works:userData$ = httpClient.get('/user')
produces anObservable
that, when subscribed, will make the backend request to fetch user data.This is again part of what makes RxJS such a powerful library. The ability to execute data fetching and other expensive tasks on demand allows for very declarative expression of complex data flows, debouncing, retry logic, etc. However, it also leads to issues when using them to drive template rendering. For example, using
userData$
in a template will work as expected:but if we also try to show the last name:
then we've accidentally caused two backend requests instead of one. This can be addressed with the
share
orshareReplay
operators, but there is no way to enforce this, especially across different components using the same Observable.RxJS is not glitch-free
This is a much more subtle but deeply important point.
In reactive systems, a glitch occurs when a calculation or reaction observes an intermediate, inconsistent state. As a simple example, consider the
counter
andisEven
example from before. If we write code that logs the message${counter} is ${isEven ? 'even' : 'odd'}
whenever either value updates, then as the counter increments from0
to1
we should observe the messages "0 is even" and "1 is odd", but never the message "1 is even". That is,isEven
should always be in sync withcounter
.RxJS does not provide this guarantee. It's easy to craft an example which shows this behavior:
Running this example gives the output which shows the glitch:
In asynchronous RxJS code, glitches are not typically an issue because async operations naturally resolve at different times. Most template operations, however, are synchronous, and inconsistent intermediate results can have drastic consequences for the UI. For example, an
NgIf
may becometrue
before the data is actually ready, which could result in readingundefined
properties. OrNgFor
may briefly observe an empty array and prematurely destroy all rows in a table only to recreate them when the real array arrives, leading to an expensive rerendering.Conclusion
Because of these incompatibilities with using Observables for template rendering, we decided against using them as the basis for Angular's reactivity. We believe their strength lies in orchestrating complex asynchronous operations, where their flexibility is a big advantage and where computational glitches are not as much of an issue. Therefore, it makes sense to instead invest in interoperability between Observables and the new signal reactivity system. This allows RxJS to be used where it shines, without forcing it to also fit the needs of template rendering.
Signals and RxJS
What about Angular APIs that currently use RxJS?
There are a number of Angular APIs that use RxJS in their API signature(ex. HttpClient, EventEmitter, Forms, Router etc.).
The general plan is to review Angular API surface and selectively convert RxJS usage and / or add signals usage when appropriate. This will not be automatic conversion, though, as RxJS-based APIs might represent two distinct concepts:
HttpClient
andEventEmitter
are perfect examples of APIs that should not be expressed as signals.In any case we will come back to the API surface review after the initial signals rollout. Any changes will be done in a backward-compatible way and subject to RFCs.
Should I use signals and/or RxJS? What is the difference and role of those primitives?
Signals would be the reactive primitive deeply integrated in the framework and thus a go-to solution for state synchronization needs. This is the first "reactive tool" that people should be reaching out for.
RxJS shines when it comes to orchestrating complex asynchronous operations. Observables power and flexibility comes at a certain cost, mostly related to the learning curve.
We definitively see use-cases where RxJS shines and solves problems in a very elegant way. This is why we are investing in a good interoperability story between signals and RxJS Observables.
Having said this, we would like to see the World where RxJS is adopted by teams as a choice for specific use-cases rather than as a mandated or default "best practice". There are many applications that can be written without complex asynchronous orchestration and we would like to encourage people to experiment with different patterns.
Introducing signals gives us a chance to try different approaches and evolve new best practices.
Why not evolve zone.js instead?
There are a few reasons why we don't think we can achieve our goals by building a better, faster version of zone.js.
The Zone approach isn't scalable.
Zone is based on monkey-patching platform APIs like
setTimeout
,Promise.then
, andaddEventListener
. This lets Angular know when something has happened in the browser, and we can then schedule change detection to synchronize the UI with any potential model changes.This approach has not scaled well. zone.js must be loaded before the application starts in order to patch its target APIs. This patching operation is not free, both in terms of bytes downloaded and milliseconds of load time. zone.js also has no idea which APIs the application will use, and must patch them just in case. As browsers add more and more APIs, the cost of this patching grows, as does the cost of maintaining zone.js.
Another major issue is that some platform APIs, such as
async
/await
, are not monkey-patchable at all, requiring awkward workarounds or forced down-leveling.While we can work to optimize zone.js and reduce these costs, we will never be able to eliminate them completely.
Zones are the root cause of many common pain points in Angular.
In looking back at 7+ years of Angular history and feedback, we've identified zone.js as a common root cause behind many developer experience or performance challenges.
The infamous
ExpressionChangedAfterItHasBeenChecked
error is often the result of violating Angular's assumptions of how data will flow in your application. Angular assumes that your data will flow from top to bottom along the view hierarchy. This assumption is rooted in how zone.js separates model updates from UI reconciliation, resulting in the potential for "unsafe" model updates.Another issue we've noted is that zone.js can easily overreact to activity in an application, resulting in many unnecessary change detections. Often this results from third-party advertising, marketing, or analytics scripts being initialized in a way that runs their many timers or other actions in the Angular zone. In the worst cases, we've seen applications which do over 1,000+ change detections at startup. It's always possible to write code with performance problems, but zone.js makes it easy to create such issues unintentionally.
Developers are opting in to more fine-grained reactivity today
Many developers today use state management patterns or libraries that architect their data in a way where they can tell Angular exactly what changed. This usually takes the form of making components
OnPush
and usingObservable
s together with theasync
pipe to mark components for check when state they depend on changes. Angular is capable of operating in this mode, but still relies on zone.js for top-down change detection. Thus, developers get some, but not all of the benefits of fine-grained reactivity, and still have to deal with the drawbacks of zone.js.Beta Was this translation helpful? Give feedback.
All reactions