DEV Community

Cover image for 4 Laws of RxJS Observables

4 Laws of RxJS Observables

Daniel Glejzner on November 02, 2023

This is crucial to understand! Online materials can often be incorrect. Very short but meaty writeup. 🏆 Here are 4 Laws of Observables...
Collapse
 
artxe2 profile image
Yeom suyun

I have never used rxjs in a real app.
However, I don't find the arguments for its usefulness very convincing.
The biggest reason is that JavaScript is single-threaded, so there is no data race.
Are there any use cases where rxjs is really helpful?

Collapse
 
lifaon74 profile image
valentin

Here's a link explaining reactive programming: core.lirx.org/docs/documentation/g...

The purpose is to work with async code. JS is single threaded as you said, and this is especially why async (Promise, Observable, EventListener) are so well integrated in the language's APIs as we MUST have a way to order and manage tasks to avoir freezing the main thread.
Reactive programming expresses its full power when mastering it, which is, to be honest, not an easy task, as it it a totally different paradigm. However, it solves a lot of async issues: avoids typical concurrency problems, automatically updated variables, etc...
This is why current front-end frameworks like react, angular, vue, svelte tends to add more and more reactivity (useSignal, useState, signal, etc...)

Collapse
 
artxe2 profile image
Yeom suyun

Thank you for your detailed answer.
However, I think my question was a bit lacking.
Rxjs has a relatively steep learning curve due to its large number of operators and paradigm differences.
The rxjs.umd.min.js file on npm is 88kb, which is even larger than jQuery.
Even if we ignore these drawbacks and don't convert events to streams, JavaScript can easily implement things like ensuring the order of asynchronous logic, debouncing, and throttling.
Therefore, I'm curious if there are any cases where RxJS is really useful.

For example, a few months ago, I searched for "Rxjs Complex example" on Google and found a source code with the same title.
However, that example could be easily implemented without RxJS, as follows.
Using async-lube instead of Rx.js

Thread Thread
 
lifaon74 profile image
valentin

I think the first step is to view the distinction between the different paradigms. Usually developers learn imperative programming. Here we speak of something completely different: reactive programming. More paradigms exist like functional programming but it's another topic. You way somehow compare it with the introduction of Promises which changed many things.

RxJs is a framework to code in reactive programming, but you may use another. Somehow, has I said, svelte or react provide tools too to use reactive programming.

Now let's explain what reactive programming solves: applications with very complex state, and very complex stream of data. When you'll work with applications having 100k lines or more, it becomes critical to have a good engine to manage a perfect consistency.
Reactive programming through Stores are a solution, as well as RxJs.
In such an application you'll encounter a lot of values (variables) depending on others (a variable computed from another + taking some data from two different API calls fo example). Here we speak about: stream aggregation, stream switching, and dynamic computed values, but many more complex use cases exist.
With reactive programming, you'll be sure to have always the correct values, automatically updated, with a perfect consistency.

About the size now: yes the lib is not light, BUT, you'll be able to do the same thing in 5-10 lines of code instead of 50+ lines of code in imperative programming, so for big applications as well as maintenance this makes a big difference.

Finally, about complex examples, I don't think it can exist. The purpose of an example is to be understandable by everyone. But complex RxJs examples would require high understanding of the framework, so it won't be relevant. Maybe, you could use chatgpt to interact with it and give you examples with explanations or take a look at the Wikipedia page en.m.wikipedia.org/wiki/Reactive_p...

However, if you want some simple example, I've created a website giving you an usecase
core.lirx.org/docs/documentation/e... . Take a look, I hoped I helped you, and any feedback is welcome 🤗

Thread Thread
 
cesard profile image
César

Going really reactive in Angular, for example, is hard to think without RxJs... I've used it EXTENSIVELY over there and now I couldn't think of working any other way in Angular... Well, at least until Signals become prod ready...
Also, RxJs becomes easier to use and understand when you get to know the usual recipes for the usual most common scenarios, like for an autocomplete, where you know you have to pipe debounceTime, distinctUntilChanges, switchMap most of the times, and with just a couple lines of code whereas in react or others is a lot more code, much more error prone and harder to maintain.
Yes, steep curve, but once you're in, it's hard to leave.

Good explanations, btw.

Thread Thread
 
artxe2 profile image
Yeom suyun

I liked the examples you provided.
I have my doubts about the usefulness of RxJS, but it doesn't seem to matter that the library used in the example is @lirx/core.
My point is not about reactive vs imperative.
It's about streams vs events.
For example, the example you provided with lirx can be written as follows

// @lirx/core
import {
  fromEventTarget,
  function$$,
  interval,
  map$$,
  merge,
  reference,
} from '@lirx/core';

const locale$ = merge([
  reference(() => selectElement.value),
  map$$(fromEventTarget(selectElement, 'change'), () => selectElement.value),
]);
const date$ = map$$(interval(1000), () => Date.now());
const output$ = function$$([locale$, date$], formatDate);
output$((value: string) => {
  outputElement.value = value;
});
Enter fullscreen mode Exit fullscreen mode
// naive
function createSignal<T>(init: T, handler: Function) {
  return {
    get value() {
      return init;
    },
    set value(value) {
      init = value;
      handler();
    },
  };
}
let react = () => (outputElement.value = formatDate(locale.value, date.value));
let locale = createSignal(selectElement.value, react);
let date = createSignal(0, react);
selectElement.addEventListener('change', (event) => {
  locale.value = (event.target as HTMLInputElement).value;
});
setInterval(() => (date.value = Date.now()), 1000);
Enter fullscreen mode Exit fullscreen mode

The naive example shows that reactive can be applied to local and date without additional learning curve, and change the value with onchage and setInterval event, respectively.

Thread Thread
 
codeawareness profile image
Mark Vasile

It's true, we can do everything we need with vanilla JS. However, building larger and larger applications you'll want to structure your code in DRY patterns, and add flexibility to calling your function with different parameters, and before long you'll discover that you've just reinvented rxjs or some other library. I think many of our performance and usability benchmarks suffer from this requirement that we show a comparison between languages/libs/frameworks by building a simple TODO list app with each.

There's also another dimension to our argument here. We are all human beings striving to understand the universe and/or build wonders. And while we can build a lot of glorious things with a hammer and a chisel, we only level up in our experience and understanding when we change our perspective. Using a different mental model is hard, that's why we tend to resist changing our ways. But it is exactly doing the same thing with different tools and different restrictions that really fuels our personal growth.

I would suggest googling differently too:

  • "rxjs vs signals" (i love signals too btw)
  • "why rxjs"
  • github.com: rxjs topic (i've found for example Akita from Salesforce, which uses exjs extensively enough to be a useful example of complex rxjs application).
Thread Thread
 
lifaon74 profile image
valentin • Edited

@artxe2 Well, actually it misses two points:

  • first cleaning up all the async tasks: in your example, you have to clean the setInterval on the 'change' EventListener, when you want to dispose of them (ex: when destroying the output element). Now, in real applications, you'll usually create dozens of async tasks per component (fetching an api, updating data, listening to user events, etc...). So, it will quickly become a mess, is error prone, hard to maintain, and often the source of memory leaks. With observable, it's all done in one call => the returned unsubscribe function.
  • with Observable it's far simpler to create very complex streams of data. In some situations, especially when you have to deal with permissions, or translations, you'll have to create a stream composed of a long chain of operators, mappers, filters, etc...

Here we speak about big and complex applications that need robustesses and consistency. For example: the drive or mail interfaces of google. Having a bug free interface with consistently fresh data and no memory leak is critical in many businesses.

Now, fundamentally, Observables are just... functions. The libraries (rxjs or lirx/core) simply provide helpers to combine and pipe them, but you may just create your own in a few lines of code. Somehow, this is like lodash but for reactive programming.

Finally, if you prefer a "sync style" you can do the same with @lirx/core signals.

Collapse
 
maxime1992 profile image
Maxime

I have never used rxjs in a real app

Please define real app. How many lines of code, trying to achieve what overall.

How do you use the HTTP API, the router one and how to you broadcast changes in your app?

The biggest reason is that JavaScript is single-threaded, so there is no data race

JavaScript being single threaded doesn't prevent race conditions in your code. JavaScript is single thread and therefore requires you to deal with queues of events, which can definitely lead to race conditions.

console.log('Hello')
console.log('World')
Enter fullscreen mode Exit fullscreen mode

and now try

console.log('Hello')
setTimeout(() => console.log('World'), 0)
Enter fullscreen mode Exit fullscreen mode

Are there any use cases where rxjs is really helpful?

When you need to deal with several asynchronous sources of data. Which, depending on the complexity of you app can happen quite a lot.

If you want a concrete example, I did write an implementation of a split flap display and explained everything in that blog post: dev.to/maxime1992/rxjs-advanced-ch...

If we summarise how the asynchronous bits are handled, here's how small it can be:

input$$.pipe(
  map(inputToBoardLetters),
  switchScan(
    (acc, lettersOnBoard) =>
      combineLatest(
        lettersOnBoard.map((letter, i) =>
          from(getLettersFromTo(acc[i], letter)).pipe(concatMap((letter) => of(letter).pipe(delay(150)))),
        ),
      ),
    BASE,
  )
);
Enter fullscreen mode Exit fullscreen mode

11 lines. To manage all the complexity of that time based, asynchronous machinery. RxJS is not helpful, it's just necessary. If you disagree, please try to implement that exact behavior without using RxJS. If it's has concise, readable and understandable as the above, I'll reconsider my answer.

Collapse
 
artxe2 profile image
Yeom suyun

I have read the attached article.
I tried writing the examples in the article without Rx.js, and I didn't find them too difficult.
However, I think Rx.js would be useful in cases where multiple events are connected to a single flow in a complex dependency chain. I haven't come across such a case yet, though.

Thread Thread
 
maxime1992 profile image
Maxime

Woups yeah, I shared the wrong article that was the one purely rxjs, which is less interactive (can't change the text on the fly). I wanted to share this one: dev.to/maxime1992/build-a-reactive...

I guess the difficulty here would be for you to cancel promises, if you've got some time feel free to give it another go and while the text is being updated, try to type in some new text while making sure the split flap display doesn't start from scratch but rather continue from where it was.

Thread Thread
 
artxe2 profile image
Yeom suyun

For some reason, an error occurs in the example you attached.
However, I have tried to add a simple function to receive user input to the previous implementation.
Because the requirements are specific, it was possible to implement it simply by adding a conditional statement to the loop, and the complexity did not increase in the asynchronous part.

Collapse
 
cesard profile image
César

RxJS is not helpful, it's just necessary.

You nailed there, man. You can say it louder but not clearer 👏🏼👏🏼👏🏼👏🏼

Collapse
 
maxime1992 profile image
Maxime

Law 1: All Observables are lazy, without exception

A BehaviorSubject reading this post:

Image description

Collapse
 
boilertom90 profile image
Tom Hoffman

If you haven’t used rxjs in an angular app, that’s a pretty simple, non-interactive app.

Collapse
 
cesard profile image
César

Or a hard-to-maintain one written in the imperative way... 😉

Collapse
 
hanss profile image
Hans Schenker

The big merit of Angular having made known Typescript and RxJs to the Web World.
Typescript and RxJs are so deep rooted in science that will outlive any Web Framework