Repository navigation
Description
Which @angular/* package(s) are relevant/related to the feature request?
core
Changelog
June 2, 2024: Added section on relying on external libraryes
Description
We have been refactoring an application towards using Angular's Signals. During this process, we observed a recurring pattern where effects inevitably require the use of untracked. This pattern has revealed several issues:
- Frequent Use of
untracked: Developers frequently need to useuntrackedto prevent unintended side effects in signals, making it a standard practice in our codebase. - Error-Prone Nature: The necessity of
untrackedis not well-known among developers, leading to common errors when it is omitted. - Maintenance Overhead: Constantly ensuring that
untrackedis correctly applied adds to the maintenance burden and cognitive load on developers.
To address these issues, we propose that Angular effects should adopt an explicit tracking model.
Proposed solution
- Explicit Declaration: Developers should explicitly declare dependencies within effects, rather than relying on automatic tracking.
- Default Non-Tracking: By default, effects should not track dependencies unless explicitly specified, reducing the need for
untracked.
Example Scenario:
Currently, developers write:
id = input.required<number>();
effect(() => {
// Part 1: Tracking
const id = this.id();
// Part 2: Execution
untracked(() => {
this.dataService.load(id);
});
})With explicit tracking, it would look like:
id = input.required<number>();
effect(this.id, (id) => {
this.dataService.load(id);
});Benefits:
- Reduced Errors: Explicit tracking minimizes the risk of overlooking
untracked, leading to more predictable and stable code. - Improved Developer Experience: Clearer dependency management simplifies understanding and maintaining the code.
- Enhanced Performance: Explicit tracking can optimize performance by avoiding unnecessary re-computations and side effects.
Internal API and Library Ecosystem:
It is noteworthy that some parts of the internal Angular API already utilize automatic untracked. While this is beneficial, other libraries face similar problems and would benefit if the untracking responsibility was handled by the caller. This approach can provide more consistent and error-free usage across different codebases and libraries.
** External Libraries:**
It has been suggested that this change be implemented as an external library instead. Most application developers are not aware of explicit tracking, though, and would not expect implicit tracking, leading to bugs. Relying on an external library requires developers to be aware of the problem and actively seek out a solution.
Reference: #56155 (comment)
Prior Discussion:
This issue was previously raised in
Thank you for considering this enhancement.
Alternatives considered
- Introducing a fourth "effect-like" function with explicit tracking
- Keep it as it is
Activity
HaasStefan commentedon May 29, 2024
I like the idea. In my opinion explicit is always better than implicit.
One syntactical consideration I would suggest is to lean towards the syntax of the useEffect hook in React, just because this might appear more common in the web dev space. Such that the explicit effect code would look like this:
rainerhahnekamp commentedon May 29, 2024
@HaasStefan, thanks. I don't really mind what the syntax looks like. I'm happy with everything as long as the tracking is explicit.
mauriziocescon commentedon May 29, 2024
IMO there is also another interesting aspect which was reported in #56098 related to the first run of effects + untracked and the reason why it's a bit special.
Unfortunately, the ticket doesn't provide an example... so here is one: https://stackblitz.com/edit/stackblitz-starters-lt4skr?file=src%2Fitems.service.ts (Note: I post the example here cause I don't want to interfere with the ticket above)
Imagine you use a service as a "store" and you want to fetch data depending on a set of parameters. Here is what you might end up with:
IMO for such use cases, skipping the first "callback" call (on top of having explicit deps) would be very handy.
solid (defer): https://www.solidjs.com/tutorial/reactivity_on
vue (watch, lazy by default): https://vuejs.org/guide/essentials/watchers#eager-watchers
JeanMeche commentedon May 29, 2024
For what it's worth, explicit dependencies has an issue, you cannot build an implicit effect on top of an explicit effect function. But the opposite is true. You can build an
explicitEffectfunction on top of the existingeffect.It makes it a prime candidate for a community lib contribution.
I'll look into landing this in ngxtension/ngxtension-platform#393
mauriziocescon commentedon May 29, 2024
@JeanMeche always our hero... thanks!
What the others are doing (just for comparison and inspiration):
I like the vue approach (new primitive like watch, effect untouched)... but this is not new! 😂
HaasStefan commentedon May 29, 2024
@JeanMeche , IMO it should be part of the core package and the default opposed to an implicit effect and a third party layer on top.
What about something along these lines:
People could still use both, but with the function overload and explicit as default, the community would be served well without the need of third party solutions.
rainerhahnekamp commentedon May 29, 2024
@JeanMeche, from an application developer's perspective I want to be in charge when I write the effect. I want to know what gets tracked. You can always provide an implicit version via overload the
effect()(or a 4th function)We strongly believe that explicit tracking is what application developers want/intend. Implicit tracking might be an additional option or something which is more relevant for library/framework developers (just guessing).
As much as I like community projects, I see a strong argument for having it in the core.
msmallest commentedon May 29, 2024
I am onboarding my team onto signals gradually on a new project, and effects in general are one of the more complex topics to explain. The first thing they ask is how and when an effect fires off its side effects, and the implicit tracking of everything by default behavior makes this a hairy issue. I know that the tracking/untracking is a key part in ones usage of effects, but having to explicitly denote something is untracked as the exception is more overhead and even confuses me. For this reason to be honest I shy away from effects for more than console logging, or I only suggest/use them for side effects we are fine with firing off implicitly all the time.
I think having effects require explicit tracking by default rather than opting out of it with
untrackedas an exception would be a better mental model. It makes the tracking model clearer, and if it is something the syntax requires denoting by default then I think the distinction between when to or not to track certain things would become more natural to think about.Explicit tracking 👍
Harpush commentedon May 29, 2024
I added an implementation idea in one of those: #54614 (comment)
garrettld commentedon May 29, 2024
I have a couple concerns with this proposal. First, I'm not sure it actually solves the problems it claims to solve. For example, let's take an effect that alerts each time the user selects an item:
Now let's implement a new requirement to include the item description in the alert. We don't want to alert when the user edits the description, so we'll need to use untracked:
and with the proposed API:
Neither of these options makes it more clear that the resulting behavior was intentional, and that we haven't accidentally introduced a bug. The maintenance overhead described in this proposal hasn't gone away, it just looks different. With the proposed API you still have to look through the effect callback (and any functions called within) to find signal invocations and determine if they were intentionally untracked.
The proposed API also creates new problems. Specifically, it requires you to "lift" your dependencies out of the effect, which has consequences for performance and readability. For example, let's imagine you're using an effect to conditionally persist changes:
current API:
proposed API:
These two examples behave the same while
autoSaveis true -- the effect is re-executed when theitemssignal changes or any item'sdirtysignal changes.However, with the current API the effect will not re-execute while
autoSaveis false, skipping some work. With the proposed API, that work (calculatingdirtyItemsand running the effect callback) will be done anyway. You could consider alternate APIs that allow for dynamic dependency arrays instead and kind of get around this, but that gets messy and difficult to maintain pretty quickly.I'd also argue that the proposed API version is less readable. Colocating the
this.items().filter(...)expression with where it's used (inside the effect callback) is easier to read than the separate computed signal in my opinion.Finally, and I think most importantly, my personal opinion is that you shouldn't be doing this in the first place.
effectis better thought of as a tool for synchronizing application state to contexts outside of Angular templates, and not as an event handler for signal value changes. Data-loading with a hand-written effect is, as you pointed out, quite annoying and error-prone. You're much better off triggering data-loading some other way (toObservable, Angular Query, Router resolvers, imperative calls in event handlers, etc.) instead. The Angular documentation mentions that "effects are rarely needed in application code" (https://angular.dev/guide/signals#use-cases-for-effects) but I wish it was worded more strongly exactly because overuse leads to the problems described in this issue.I think the underlying problem here is that it seems like
effectshould work well for things like triggering data loading. React has the same problem withuseEffect(see https://react.dev/learn/you-might-not-need-an-effect). I would love to see more exploration of this problem space, particularly around rxjs interop and other async/data-loading primitives, but I think that making effect dependencies explicit creates more problems than it solves.e-oz commentedon May 29, 2024
I'm pretty sure about two things:
effect()is too late. v18 is out, developers already use signals, and a lot of them useeffect()(because most programmers write imperative code). It would cause a lot of negative feedback;effect()with explicit deps tracking. Any community-provided function will be met with skepticism - "If Angular devs would think we need it, they would provide it in the core, it means we maybe don't need it". Angular always was a "batteries included" framework. It will be quite difficult to convince users to use a more safe version ofeffect().For readers who don't have a clear picture of what this is all about and what the possible issues are: you can find examples in my article.
I disagree, @garrettld .
effect()is not for modifying the state, it is for side-effects. To update a DOM, to re-render some map... for dirty imperative work. For computing the state we havecomputed().63 remaining items
feat(core): explicit effect
feat(core): explicit effect
feat(core): explicit effect
feat(core): explicit effect
feat(core): explicit effect
feat(core): Introduce `splitEffect`, an `effect` with an untracked ef…
feat(core): Introduce `splitEffect`, an `effect` with an untracked ef…
feat(core): split tracking for `effect` and `afterRenderEffect`
feat(core): split tracking for `effect` and `afterRenderEffect`
feat(core): split tracking for `effect` and `afterRenderEffect`
JeanMeche commentedon May 24, 2026
Fiddling with some ideas in #68703 and #68908.
The latter is more versatile and would prevent API bloat.
wall-street-dev commentedon May 25, 2026
I can't believe that in the Year of the Lord 2026, this thread is alive because some devs still cannot wrap their heads around a simple untracked inside an effect body. Do you need a daily reminder to wipe your arses? My goodness.
mauriziocescon commentedon May 25, 2026
I’d probably leave everything as it is as well.
The situation has vastly improved over the last two years, and the need for 'effect' has drastically decreased. The minor annoyance of using untracked is actually a good thing in a way: it reminds you, 'Maybe there’s a better approach than an effect?'
For everything else, there are custom wrappers (and technically speaking, you can use untracked inside a computed as well 😅)."
☮️
imaksp commentedon May 25, 2026
this looks like a helper which uses untracked, IMO new primitive like Vue's watch (https://vuejs.org/guide/essentials/watchers#eager-watchers) would be more helpful. (for example with signal forms)
JanTheMan1010 commentedon Jun 3, 2026
I love this, since it's aligned with the rxresource-syntax. Im curious why we can't integrate @JeanMeche "explicit-approach" in effect: