Skip to content

toSignal causes the signal to not being lazy-evaluated #55337

@csisy-bt4w

Description

@csisy-bt4w

Which @angular/* package(s) are relevant/related to the feature request?

core

Description

First, we implemented a model (with validation logic and so on) with signals. Howver, since a signal/effect cannot be created anywhere (must be in injection context and outside of reactive context), we opted out and decided to use rxjs instead. This is fine, since the model is actually independent of Angular itself.

Additionally, we have a component that consists of pages. Each page can display validation errors found on that given page. The validation errors come from the model stated above.

Previously, while the model used signals, we were able to take advantage of the lazyness of the signal read operations. Consider the following simplified example:

// model class
readonly errors = computed<string[]>(() => ...);

// page class
readonly model = input.required<Model>();
readonly visited = signal(false);
readonly pageErrors = computed(() => this.visited() ? this.model().errors() : []);

So when we used (read) the pageErrors we only evaluated the model's errors if the page were ever visited.

Using signals in components is comfortable so we used the toSignal from rxjs-interop, something like this:

// model class
readonly errors$: Observable<string[]> = ...

// page class
readonly modelErrors = toSignal(model.errors$, { initialValue: [] });
readonly pageErrors = computed(() => this.visited() ? this.modelErrors() : []);

However, the toSignal call has a non-trivial consequence that it subscribes immediately, losing the lazy-evaluation aspect. The other way around (toObservable) is pretty much the same, maybe even worse (since it uses effect in its implementation that always runs at least once).

Proposed solution

I think the toSignal and toObservable should get a deeper integration, allowing us to avoid use-cases like this. It would be the best if all computed signal properties (lazy + memoized) could be kept when using toSignal - only subscribe when the signal value is read, and unsubscribe when the signal's refcount becomes 0 (all its listeners are unsubscribed).

Alternatives considered

Currently there is no appropriate workaround, at least we could not find any. We could opt-out of using signals, but it has its advantages that we'd like to keep.

Metadata

Metadata

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions