Seeing unexpected excitement from the RxJS community — turns out you can almost use RxJS observables natively in Svelte. The place where it falls down is unsubscriptions: https://mobile.twitter.com/Rich_Harris/status/1121042764429438976.
If there was a way to make RxJS support dead simple, a lot of people would find it useful. Could mean either
subscribe functions that either return an unsubscribe function (as currently) or an object with an unsubscribe method, when setting up subscriptions with the implicit $foo$ form (the trailing $ is an Rx convention). Maybe we don't want to play favourites though (but it would be an easy and small thing to add, I think)Supporting { unsubscribe() } would be useful for those of us who built up infrastructure around that convention from svelte@2, so I'd be very in favor of that.
The cost seems minimal, I see no reason not to make this change considering the above benefits.
Automatic RxJS support would be a great improvement would be to Svelte, to make RxJS usable in component templates with certainty of automatic unsubscription and avoiding needless duplicate subscriptions.
How about reaching a bit further though?
Let developers trivially use an observable in a template, or use it as the thing being looped over in “each” etc., and have it “just work”. No extra syntax. No "each". Automatically unwrap the observability and do the right thing. Exact same template syntax for variables of type string, and of type Observable
I also argue that right now is a good time to consider such a bold level of support. Over in my other favorite framework (Angular) it is arguably too late to do this because it could break semantics or break user expectations of a huge developer ecosystem.
@kylecordes I'm not sure I follow what you mean by 'Exact same template syntax for variables of type string, and of type Observable' over and above what #2556 gives us?
Sorry for the fuzziness. When I hurriedly wrote the above, I said "no each" but meant "no $". Here’s what works right now:
<script>
import { interval } from 'rxjs';
let count = interval(400);
</script>
<h1> The count is {$count}</h1>
Here’s what I’m wondering about:
<script>
import { interval } from 'rxjs';
let count = interval(400);
</script>
<h1> The count is {count}</h1>
In other words, have observable values "Just Work" rather than treat them as special, as Stores etc. Reactivity as default built-in always-on, rather than being reached for (albeit with only a single character).
The challenge here, until type information is comprehensive of the available it's not clear how to do this with full efficiency. Once the type system can support telling the template compiler which values are type Observable, then it becomes "free" at runtime for the compiler to infer the $, right?
I don't see why Svelte would want to make such a concession for a single state management tool, if we do it for RxJS why shouldn't we do it for other widely used state-management libraries. This issue was really just about making a minor change that would prevent memory leaks, making RxJS observables usable out of the box.
In this context, RxJS Observables aren't being treated like Stores, they are stores. They fulfill the store contract. And as such they need to use the required syntax in order to benefit from automatic subscription and unsubscription. I see no reason to make special allowances for RxJS here.
You say 'don't treat them as special' but the proposed change would be treating them as special, that behaviour would not be consistent with how the svelte API works in general.
@pngwn Indeed, the thing I'm talking about, would ideally not be RxJS-specific or -aware at all. Rather, how about if store/async/observable/etc. data, regardless of library, would "just work" in a Svelte template?
Or put a different way, how about if plain local component state variables were no longer privileged to be consumed in a special way (without the $ prefix), but rather, both local state variables and higher order state (be it RxJS, svelte/store, etc.) were both first-class?
Because, outside of technical concerns, the syntax allows us to easily differentiate between normal values and subscribables. The extra syntax is a tool for users of the framework that allows for greater clarity.
Svelte has a few simple rules around reactivity. Reactive labels ($) for local reactive values, assigments operators (=, etc) for 'manual' reactivity and store prefixes ($value) for subscribables. I think these rules are relatively straightforward. Forcing users to understand the implementation details of each value in order to reason about their component code will lead to a worse user experience, in my opinion. Especially when you consider that not all stores will be written directly by the user, they could be consuming them via a third party library.
Here's a far future prediction: a framework/language of the future, will treat observable/reactive data as first class, and treat local state as either equally first-class, or behind some extra syntax. When the time comes for this, it will be obviously right. There is a progression something:
Maybe now is not the time yet - today too much of the user base (including third-party libraries as you describe) would see it as being forced to understand something they don't want to understand, versus seeing it as a higher abstraction. I'm certainly not going to beat a dead horse on it, though I am curious what @Rich-Harris thinks also.
@kylecordes the problem with your suggestion is that omitting the $ in $value means that Svelte has to check, at runtime, whether value is a store or, to avoid false positives, would have to wrap everything in a store. I don't think either option is ideal.
@steve-taylor Yes, that's why I think this probably is not feasible in an efficient way until there is comprehensive TypeScript support. The types could inform the compiler how to handle a particular variable.
(More generally, type aware compilation can produce better/smaller output in other ways also.)
TypeScript
Svelte v4, perhaps.
I'm inclined to agree with @pngwn, largely because I've made the mistake of banking on a Sufficiently Smart Compiler too many times in the past. Even assuming perfect typing information (and thereby excluding non-TypeScript users, I think it puts us in a worse position, because of things like this:
<!-- this works today -->
<p>{$a} + {$b} = {$a + $b}</p>
<!-- what would this mean? -->
<p>{a} + {b} = {a + b}</p>
It's one thing to treat the expression inside a curly brace as potentially being an observable, but to treat things within an expression as observable introduces a lot of ambiguity. I much prefer the explicitness of 'here is a store, and here is the store value'.
Thank you for looking at my suggestion - the point about still supporting JavaScript users is especially key, I think will a long time before it is "OK" in the overall web community to not support untyped code. I shall slink back to typing “$” without comment :-)
I see where @kylecordes is coming from. I understand a store to be an abstraction for a (continuous) stream of values. A store always has a value, which can be queried (sampled) at any time via the appropriate sampling operator. The DOM is also a continuous stream of values. I guess Kyles want to write the DOM as an equation between stream of values. To refer to the example: $DOM = <h1> The count is {$count}</h1>. You could then sample the DOM stream with rAF.
There are functional languages such as Lucid Synchrone or Zelus which have implemented reactivity in the way Kyles suggest. However, they had to be very strict regarding the treatment of time, and the types they allow. Typically in Lucid, everything is a stream, and time is discrete, and represented by a clock abstraction. While the end result is indeed elegant, and in many ways so simple, and made its way into industrial modelling tools for embedded systems (with a natural concept of clock) such as SCADE, it is not easy to directly extend to asynchronous contexts.
About API for stores, to stay from vendor-specific API, why not use the TC39 observable proposal?
interface Observable {
(...)
// Subscribes to the sequence with an observer
subscribe(observer : Observer) : Subscription;
(...)
// Subscribes to the sequence with callbacks
subscribe(onNext : Function,
onError? : Function,
onComplete? : Function) : Subscription;
(...)
}
interface Subscription {
// Cancels the subscription
unsubscribe() : void;
(...)
}
That is pretty much the rxjs way actually, but it is not the rxjs interface, it is the TC39 interface and that goes in the direction of Svelte of supporting standards..
@brucou The relationship between Svelte stores and the TC39 Observable Proposal was discussed in the Reactive Store RFC.
I read that. Yeah observables can be a bit tricky to grok indeed, mainly because of the difference between the concept and its implementation. A rxjs observable is for instance not a stream, but encloses a stream factory or more commonly called a producer -- that is how they say observables are cold. Because it has a factory, it has to be started, i.e. the factory has to be executed -- that is when you .subscribe(...). And what you terminate (with .complete()) is not the stream, it is the producer. And that is just the beginning for the source of confusion. Now if implementations are confusing, the concept itself is pretty simple.
I reviewed svelte's store and conceptually stores seem to be a mix of lenses and what is called behaviours in FRP terminology. A behaviour is denotationally a continuous function of time, say the behaviour b is a function t -> b(t) and you can only observe (sample) its values at a given time. Say at t0 you will observe b(t0)=b0. A behaviour neither starts nor terminates, and can't be updated, it is just a function. Lenses is a functional tool which allows to enter a structure and get/update a part of it.
Sorry if I over-extended myself. Back to the subject matter, I see a lot of benefits to supporting rxjs/tc39 observer interface, given the small surface area of the change.
I agree this shouldn't be exclusive to RxJS. A huge contingent of ppl us Most.JS Core and other observable patterns and it would be best not only to support RxJS but them too.
Any third-party observable pattern can be used with Svelte by writing an adapter. This has always been the case.
Native support for RxJS in particular was opted for because:
We don't want Svelte core to be bogged down with support for a bunch of relatively obscure observable implementations. Whether there should be official adapters for them is a separate question, but native support for them should not be a goal just because RxJS was added.
What is the current status of the integration between Svelte and RxJS? It would be also be useful to see something documented about best practices and where the boundaries between the two APIs are.
So, can i curently use rxjs with svelte? Was the unsubscribe thing implemented?
@Mateiadrielrafael You can use it right now. We are. But there is a big caveat. To meet the "store contract", your observable needs to emit its first value immediately. If you have an observable chain that may take a while to emit its first value, you'll typically need a "startWith" or "publishReplay(1)" or similar depending on the situation.
Also this works fine:
<script>
import { onDestroy } from "svelte";
export let authStore;
let user;
const unsubscribe = authStore.user.subscribe(value => {
user = value;
});
onDestroy(() => unsubscribe());
</script>
<main>
<h1>Protected</h1>
<h2>Hi {user.username}</h2>
</main>
But this does not
<script>
export let authStore;
</script>
<main>
<h1>Protected</h1>
<h2>Hi {$authStore.user.username}</h2>
</main>
I have also tried authStore.$user.username
@alshdavid If you create something like this in a form it runs/fails in the REPL, likely someone can easily point out a syntax variation that works.
I see. I am using codesandbox so perhaps that is the issue.
Here it is. The code in question is /pages/protected.svelte
https://codesandbox.io/s/vigorous-satoshi-z3jyv
As an alternative, you can wrap an rxjs observable in a hook-like function
// use-subscribe.js
import { onDestroy } from "svelte";
export const useSubscribe = (rx, cb = () => {}) => {
const unsubscribe = rx.subscribe(cb);
onDestroy(() => unsubscribe());
};
// component.svelte
<script>
import { useSubscribe } from "../use-subscribe";
export let authStore;
let user;
useSubscribe(authStore.user, v => (user = v));
</script>
<div>Hi {user.username}</div>
Hmmm, so it doesnt unsubscribe by itself...
@Mateiadrielrafael Yes, it does unsubscribe. Svelte treats an Rx observable like a store; you access it with the $ prefix and it mostly Just Works. I say mostly because it is unclear to me whether it is ever OK for a Svelte store to not emit its first value immediately, while it is OK for an Observable to do so. I've occasionally edited my code a bit to ensure some value is emitted upon subscribe.
@alshdavid What is the benefit of writing that code above, given that Svelte already treats an Rx observable as a store: subscribing, updating, unsubscribing for you?
@kylecordes So i should always use BehaviorSubjects or the shareReplay operator?
I'd also be very interested in using rxjs with svelte, same as MVSICA-FICTA my question is wheter or not there's some sort of best practices already and if that's documented somewhere?
This is a new area for Svelte. I'm using it this way, but I haven't been following discussion on Discord recently and don't know if anyone else is.
About @Mateiadrielrafael 's question, that is a good starting point simplification. You can often get by with just a startWith, but sometimes a shareReplay, which behind the scenes is basically creating a BehaviorSubject, makes more sense. I don't have some kind of simple iron rule to follow though. I approach this from the point of view of having used RxJS extensively for years.
@evdama I don't think there is any significant documentation yet, there probably needs to emerge a community of practice out of which that could arise. For the moment though, just that key rule I mentioned (to follow the Svelte store contract, your observable ought emit immediately upon subscription) will hopefully get you through, if you're generally experienced with RxJS.
I'm going to close this, as I believe for some time we've had the issues ironed out with consuming RxJS observables anywhere that we can autosubscribe to a store.
@kylecordes
The types could inform the compiler how to handle a particular variable.
Just for your info, maybe you understood in the mean time:
TypeScript only uses types for type checking. It doesn't emit different JavaScript when only the types differ. That's one way it stays compatible with JavaScript.
Other compilers, like Babel, can simply remove type info from typescript code to output valid JavaScript.
So what you said here is impossible.
Most helpful comment
Any third-party observable pattern can be used with Svelte by writing an adapter. This has always been the case.
Native support for RxJS in particular was opted for because:
We don't want Svelte core to be bogged down with support for a bunch of relatively obscure observable implementations. Whether there should be official adapters for them is a separate question, but native support for them should not be a goal just because RxJS was added.