You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Trying to be brief this time, I want an easy and safe way to call .now() on signals, without sacrificing safety, and without complicating the architecture. The linked issues above explain why this is hard to achieve in the current design, and possible strategies for improving the situation.
Note that this problem is specific to limited-lifetime situations, e.g. all your components' code, that is linked to element mount-unmount lifetimes. For global things that never need to be destroyed, using unsafeWindowOwner is a perfectly valid strategy.
Proposed solution
// Example is a class just to show that you're not limited to functions returning a single element.classMyTextInput(savedTextS: Signal[String], signal2: Signal[String]):valclickBus:EventBus[Unit] =newEventBusvalnode:Div=
div(
cls("MyTextInput"),
).observe(savedTextS, signal2):
(thisNode, savedTextS_, signal2_) =>
...
valtextInputVar=Var(initial = savedTextS_.now())
...
thisNode.amend(
onClick.mapToUnit --> clickBus,
input(
onInput.mapToValue --> textInputVar,
value <-- textInputVar
)
div.observe is the new thing here. You can call this new observe method on any element, provide it 1...N signals, and provide a callback that receives this same element as well as all of these signals converted to StrictSignal, allowing you to query its current value (.now()) at any time. This signal will only update while the element is mounted. When the element is unmounted, it will stop updating, until it is mounted again.
Importantly, the render callback of this observe method ((thisNode, savedTextS_, signal2_) => ...) is called at most once – when the element is first mounted (or immediately when it is invoked, if the element is already mounted by then). This is key.
This is different from methods like onMountInsert – the callback in those methods is called every time the element is mounted – this is why we have several versions of this method like onMountBind, onMountSet, and onMountInsert – we can't just put arbitrary modifiers into a generic onMount method, because arbitrary modifiers are not necessarily idempotent.
But with this observe method, we can! We can put anything we want in thisNode.amend, or do any other things. The observe method will return the original element for easy chaining.
Observed signals' lifetime
To reiterate, the observe method's render callback is only called if and when the element is mounted. So if we create this element but never mount it, the element never starts observing the signals, and we never get access to them. So if we do have access to the signals, it means that they have some value, they can't have no value at all. This is an improvement over the peekNow() proposal which would see us face exceptions in such cases.
As a user, you could potentially allow observed signals (savedTextS_ and signal2_) to escape the scope of the render function, for other code to access it. This is not intended use, but it's possible. In that case, these signals will continue having updating their values for as long as the original element that observed them is mounted, but as mentioned before, when it is unmounted, the signals would stop updating. Unless some of your other code adds observers to them, of course.
Ergonomics
Unlike onMountInsert, the observe method returns the element itself, not an opaque Inserter type. So, it can be used inside the split method's callback, inside child <--, children <--, etc.
Unlike child-specific owners (#148), this can be used on any element, inside or outside of split.
I'm not a fan of the boilerplate (duplicating the lists of signals in the observe call's arguments), but I don't see how it can be avoided, and the benefits of the design outweigh the annoyance, I think.
Use cases
Currently this feature is exclusively about signals and getting their .now() value. I assume that raquo/Airstream#119 will obviate the need for supporting zoomed vars. I don't see other use cases, it seems to be pretty much just this one rough edge that needs fixing.
Implementation
Each Laminar element has a DynamicOwner. Calling observe would create a new DynamicSubscription that, for each provided signal, would create a special type of StrictSignal that would be updated whenever DynamicOwner is active.
I would need to check the type hierarchy of StrictSignal / ObservedSignal / OwnedSignal to see if it still makes sense, and clean up / amend as necessary.
We would need to source-generate observe methods with different arities like we do for e.g. combineWith methods.
Overall this seems like a pretty small change technically, compared to the other referenced issues.
Architecturally, this feature should have no negative impact on other planned features, it is quite self-contained.
The text was updated successfully, but these errors were encountered:
One variation of this could be an observe(signal1, signal2) modifier that you put in a div, combined with allowing reading of signal values with .now(). The observe would ensure that the signals that you call .now() on are started, but there would be no static type safety for that. If you forget to observe a signal that you call .now() on (but don't use in any other way), calling .now() would start the signal, and pull its value from parent signals. This may not give you the value that you want if the signal depends on streams (e.g. via flatMap or composeChanges) since the streams would have no time to run.
So, you would be able to call .now() any time, it would always "work", but in some cases could give you a stale value if you didn't observe the signal (whether using the new observe modifier, or by adding observers the usual way).
Not convinced yet that this is a good approach. I need to do more reading / thinking on this whole thing.
Background
This is yet more rumination on the issue of lifetime evidences:
Problem
Trying to be brief this time, I want an easy and safe way to call
.now()
on signals, without sacrificing safety, and without complicating the architecture. The linked issues above explain why this is hard to achieve in the current design, and possible strategies for improving the situation.Note that this problem is specific to limited-lifetime situations, e.g. all your components' code, that is linked to element mount-unmount lifetimes. For global things that never need to be destroyed, using
unsafeWindowOwner
is a perfectly valid strategy.Proposed solution
div.observe
is the new thing here. You can call this newobserve
method on any element, provide it 1...N signals, and provide a callback that receives this same element as well as all of these signals converted toStrictSignal
, allowing you to query its current value (.now()
) at any time. This signal will only update while the element is mounted. When the element is unmounted, it will stop updating, until it is mounted again.Importantly, the render callback of this
observe
method ((thisNode, savedTextS_, signal2_) => ...
) is called at most once – when the element is first mounted (or immediately when it is invoked, if the element is already mounted by then). This is key.This is different from methods like
onMountInsert
– the callback in those methods is called every time the element is mounted – this is why we have several versions of this method likeonMountBind
,onMountSet
, andonMountInsert
– we can't just put arbitrary modifiers into a genericonMount
method, because arbitrary modifiers are not necessarily idempotent.But with this
observe
method, we can! We can put anything we want inthisNode.amend
, or do any other things. Theobserve
method will return the original element for easy chaining.Observed signals' lifetime
To reiterate, the observe method's render callback is only called if and when the element is mounted. So if we create this element but never mount it, the element never starts observing the signals, and we never get access to them. So if we do have access to the signals, it means that they have some value, they can't have no value at all. This is an improvement over the
peekNow()
proposal which would see us face exceptions in such cases.As a user, you could potentially allow observed signals (
savedTextS_
andsignal2_
) to escape the scope of the render function, for other code to access it. This is not intended use, but it's possible. In that case, these signals will continue having updating their values for as long as the original element that observed them is mounted, but as mentioned before, when it is unmounted, the signals would stop updating. Unless some of your other code adds observers to them, of course.Ergonomics
Unlike
onMountInsert
, theobserve
method returns the element itself, not an opaque Inserter type. So, it can be used inside thesplit
method's callback, insidechild <--
,children <--
, etc.Unlike child-specific owners (#148), this can be used on any element, inside or outside of
split
.I'm not a fan of the boilerplate (duplicating the lists of signals in the observe call's arguments), but I don't see how it can be avoided, and the benefits of the design outweigh the annoyance, I think.
Use cases
Currently this feature is exclusively about signals and getting their
.now()
value. I assume that raquo/Airstream#119 will obviate the need for supporting zoomed vars. I don't see other use cases, it seems to be pretty much just this one rough edge that needs fixing.Implementation
Each Laminar element has a
DynamicOwner
. Callingobserve
would create a newDynamicSubscription
that, for each provided signal, would create a special type of StrictSignal that would be updated wheneverDynamicOwner
is active.I would need to check the type hierarchy of StrictSignal / ObservedSignal / OwnedSignal to see if it still makes sense, and clean up / amend as necessary.
We would need to source-generate
observe
methods with different arities like we do for e.g.combineWith
methods.Overall this seems like a pretty small change technically, compared to the other referenced issues.
Architecturally, this feature should have no negative impact on other planned features, it is quite self-contained.
The text was updated successfully, but these errors were encountered: