-
Notifications
You must be signed in to change notification settings - Fork 24.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(core): untrack various core operations #54614
Conversation
1b2ee78
to
94a4936
Compare
Hello. What do you think about changing effect()'s API a little, to run its function always outside of the reactivity context: const a = signal(0);
const b = signal(0);
effect([a, b], ( [a, b] ) => {
if (a < 1) {
console.log(b);
}
}); The first argument is the list of signals to listen. It is still possible to trigger an endless loop here (if |
This is great for library code as there nothing Angular needs to do to support this API in the outside. |
Actually this can be implemented now in user code... although I agree it should be inside angular core as it is useful: type SignalsArrayValues<T extends Signal<any>[]> = {
[Index in keyof T]: T[Index] extends Signal<infer P> ? P : never;
};
function explicitEffect<T extends [Signal<any>, ...Signal<any>[]]>(
signalDeps: T,
effectFn: (
...params: [
...deps: SignalsArrayValues<T>,
onCleanup: EffectCleanupRegisterFn
]
) => void,
options?: CreateEffectOptions | undefined
) {
return effect((onCleanup) => {
const deps = signalDeps.map((signalDep) =>
signalDep()
) as SignalsArrayValues<T>;
untracked(() => effectFn(...deps, onCleanup));
}, options);
}
// Usage:
const a = signal(2);
const b = signal('s');
const c = signal(true);
explicitEffect([a, b], (a, b) => console.log(a, b, c()));
setTimeout(() => {
// Triggers
a.set(7);
}, 2000);
setTimeout(() => {
// Won't trigger
c.set(false);
}, 4000); |
94a4936
to
92adae1
Compare
My idea is about a paradigm shift for Automatic tracking of dependencies is one of the selling points of Angular Signals. If But there would be a positive outcome too:
|
I support @e-oz in tracking dependencies explicitly. It could also be done via a fourth function, as suggested by @markostanimirovic in #54548 (comment). Personally, I'd prefer overloading
I would also mark the current signature of |
Having explicit tracking by default can avoid a lot of confusion. I also think we should teach alternatives to effect but there are some situations where effects cannot be prevented. |
One downside of implicit dependency tracking in `effect()`s is that it's easy to for downstream code to end up running inside the effect context by accident. For example, if an effect raises an event (e.g. by `next()`ing a `Subject`), the subscribers to that `Observable` will run inside the effect's reactive context, and any signals read within the subscriber will end up as dependencies of the effect. This is why the `untracked` function is useful, to run certain operations without incidental signal reads ending up tracked. However, knowing when this is necessary is non-trivial. For example, injecting a dependency might cause it to be instantiated, which would run the constructor in the effect context unless the injection operation is untracked. Therefore, Angular will automatically drop the reactive context within a number of framework APIs. This commit addresses these use cases: * creating and destroying views * creating and destroying DI injectors * injecting dependencies * emitting outputs Fixes angular#54548 There are likely other APIs which would benefit from this approach, but this is a start.
92adae1
to
3b967b3
Compare
The current For example, let's consider a simple use case: We have a signal I saw on reddit almost same disuccsion with a lot of different answers: https://www.reddit.com/r/Angular2/comments/1b0in0y/react_to_state_change_in_angular/ With Signals we have another option to manage state. It should be more clear what the best practices are. |
@hannie I feel you. I think, in most cases, you can just use the event that leads to the change in the Saying this, I think there are a few corner cases where this strategy does not work, and all solutions like |
This PR was merged into the repository by commit ffad7b8. |
One downside of implicit dependency tracking in `effect()`s is that it's easy to for downstream code to end up running inside the effect context by accident. For example, if an effect raises an event (e.g. by `next()`ing a `Subject`), the subscribers to that `Observable` will run inside the effect's reactive context, and any signals read within the subscriber will end up as dependencies of the effect. This is why the `untracked` function is useful, to run certain operations without incidental signal reads ending up tracked. However, knowing when this is necessary is non-trivial. For example, injecting a dependency might cause it to be instantiated, which would run the constructor in the effect context unless the injection operation is untracked. Therefore, Angular will automatically drop the reactive context within a number of framework APIs. This commit addresses these use cases: * creating and destroying views * creating and destroying DI injectors * injecting dependencies * emitting outputs Fixes #54548 There are likely other APIs which would benefit from this approach, but this is a start. PR Close #54614
@pkozlowski-opensource, @alxhub, I think we had a good discussion about a potential future for the effect here. Is there a GitHub issue, which is dedicated to that topic and where we could continue? |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
One downside of implicit dependency tracking in
effect()
s is that it's easy to for downstream code to end up running inside the effect context by accident. For example, if an effect raises an event (e.g. bynext()
ing aSubject
), the subscribers to thatObservable
will run inside the effect's reactive context, and any signals read within the subscriber will end up as dependencies of the effect. This is why theuntracked
function is useful, to run certain operations without incidental signal reads ending up tracked.However, knowing when this is necessary is non-trivial. For example, injecting a dependency might cause it to be instantiated, which would run the constructor in the effect context unless the injection operation is untracked.
Therefore, Angular will automatically drop the reactive context within a number of framework APIs. This commit addresses these use cases:
There are likely other APIs which would benefit from this approach, but this is a start.