Skip to content
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

docs: add a best practices document #6157

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Armenvardanyan95
Copy link
Contributor

This is an initial version of a document describing best practices and antipatterns to avoid when using RxJS. This is a more finalized version of my draft, I tried to bring together important points mentioned by everyone in the related issue thread (#5185). Will be glad to hear how this can be improved

@Armenvardanyan95
Copy link
Contributor Author

Important questions to be addressed:

  • Do we need any more sections?
  • Is the reason why subclassing is bad explained enough?
  • Are the code examples okay?
  • I did not update the navigation.json file, as I did not really know where should this go, if anyone directs me, I will also update that

WIll happily listen to everyone's thoughts

Copy link
Member

@niklas-wortmann niklas-wortmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi sorry I wasn't able to review this earlier. First of all thanks for taking the time for this contribution, I do think that this is a gap in the docs we have to fill. From my point of view your suggestions have a couple personal opinions, that I don't necessarily share and I am not sure how we should incorporate those properly in the docs. Maybe other core team members have an opinion as well, @cartant, @benlesh, @kwonoj, @kolodny

@@ -0,0 +1,100 @@
### RxJS best practices
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the headline structure is kinda weird, why is this a h3? Shouldn't this be an h1?


RxJS is very large and contains lots of functions, operators and other stuff which makes it a complex ecosystem, but for lots of applications it can be as simple as knowing what `Observables` are and what can be done with them with some `operators`. But for larger codebases it is important to have code written in a way that allows for reusability, readability and composability. This guide may help overcome common ba practices and adopt a reactive mindset necessary for righting concise RxJS code.

## .subscribe ends composability
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't fully agree with this and would not put this in a best practice guide, but maybe the other core team members have a different opinion

When we call `.subscribe` on any `Observable` what we do is get rid of said `Observable` and then only focus on the values that it produces. When we call `.subscribe` we can no longer continue composing operators (`.subscribe` returns a `Subscription`, on which we can no longer call `.pipe` and use RxJS operators), and composability is **the** greatest feature of RxJS. Obviously, without subscribing to most `Observables`, we won't have a working application. In practice, frameworks like Angular offer solutions that allow to use `Observable` values directly without explicitly subscribing to them, a great example is the `async` pipe.
So while subscribing is obviously not directly discouraged, we should be generally very careful when doing it, as to understand that the stream in question is no longer to be modified and can only be used as a source of values, nothing more.

## Using imperative logic in .subscribe instead of operators
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't necessarily agree with this. There are valid use cases and there is nothing wrong with this approach. I actually think that people more likely harm readability by leveraging all weird operators


Another problem that developers face when using `.subscribe` is performing complex operations on values from a source `Observable`. Especially bad scenarios involve using conditional operators and loops inside `.subscribe` callbacks, instead of composing `Observables` using pipes. Here are some common mistakes:

### Using an if statement when subscribing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not consider this a best practice. It's more of a preference from your side.

});


### In general, heavy logic when subscribing is discouraged
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, I don't really agree with this. There is nothing wrong with subscribing to an Observable it is actually required and this might be more confusing for newcomers.

takeUntil(notifier$),
).subscribe(console.log)

In this example just looking at the `Observable` is enough to know exactly when it will terminate: when the `notifier$` `Observable` emits. Also, this is the widely accepted approach in the Angular community.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs are and should be framework agnostic so I would prefer not to refer to angular here


## Avoiding memory leaks

The way RxJS operates creates a way for memory leaks to appear in applications that use it. Usually it happens when a consumer subscribes to an `Observable` and the `Observable` itself never terminates naturally (for example, an `Observable` created by `interval`, which emits values in equal periods of time), and no one unsubscribes from it or terminates it manually. The simplest way to do that is directly unsubscribe when the subscription is no longer necessary:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like a little bit more explanations here, how this problem can happen and what the problem would cause. This might be very hard to understand for people new to rxjs


### Be careful to avoid takeUntil leaks

When `takeUntil` is not used correctly, it can lead to memory leaks too. As a rule of thumb, always place `takeUntil` as the last operator in the `pipe`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate on this a little bit to make it easier to understand for newcomers


## Don't subclass

Sometimes, it can be tempting to build our own version of an `Observable` using existing classes. In particular, some developers might extend the existing `Observable` class to add some functionality and then use the "better" version throughout the app. But problems can arise in the future, for example, if the base classes are updated with the new versions of the RxJS library, as those classes are tightly coupled. Also, implementing a bae class that can work requires the developer to implement the `lift` method, which in turn makes the consumer application aware of the internal implementation details of the library. This [thread](https://github.com/ReactiveX/rxjs/issues/5431) contains a detailed discussion of the matter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a typo in bae

@cartant
Copy link
Collaborator

cartant commented May 5, 2021

Maybe other core team members have an opinion as well

I don't think this could be merged without discussion. I don't agree with everything in this PR and, FWIW, I'm reluctant to declare a list of official best practices.

@CMCDragonkai
Copy link

Regarding the don't subclass section. Can you provide an alternative to this kind of usecase?

Suppose you had a domain class like KeyRing. The KeyRing is a class with properties and methods. You want the properties to be observable, and that's easy enough with third party observable decorators, but what if want the whole KeyRing object to be observable too?

Would that mean some sort of BehaviourSubject for the KeyRing object, so that downstream consumers can subscribe to the entire KeyRing and should get the pointer to the KeyRing object, and access all the changed properties atm.

One issue I'm thinking is that the KeyRing is a singleton object, it's not like new KeyRing is created on each change. That could meant it doesn't make sense for it to be a reactive class itself, but I'm not sure. There's not much information on whether it makes sense to subclass Observable with your own custom class, in which you want the whole domain object itself to be observable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants