RxJSBetter Practice

Photo byOlav Ahrens RøtneonUnsplash

RxJSis a real life savior when it comes to the app with complex data structuring, helps building solid data pipelines easy and elegant.
Like any other tools, this conveniency and simplicity comes with the chance of messing up if you don’t know the proper use case, that requires the knowledge of which is theBetter Practice(still figuring out what is the best).

This is from KnowledgeShare at my team and based on some mistakes we’ve seen. Some are silly and some are critical to the app.
And this is not the officialBetter Practice, but collected from real cases, it might not apply to your app, but good to know these bad and better cases .

So Let’s share it.

| Split logic with operators

Simple and Easy first.
Do not put every operations in the observer function(next subscriber).
Use Operators to lift the burden of them off and make easy to follow the stream and keep function light.

// can be better
obs$.subscribe((state)=>{
if(!state) return;
const newState = state.map(...);
this.setState(newState);
});
// is better
obs$.pipe(
filter(Boolean),
map((state)=>{...})
).subscribe((newState)=>{
this.setState(newState);
})

| Flatten subscribe function

我们先w callback hell, and it applies to rxjs observer function too.
Do not use subscribe inside subscribe. You have plenty of **map and join operators which flatten the stream.

Want to read this story later?Save it in Journal.

// can be better
obs$.subscribe((state)=>{
const obs2$ = ... || ...;
obs2$.subscribe((state2)=>{
const { a } = state;
const { b } = state2;
this.setValue(a * b);
})
});
// is better
obs$.pipe(
switchMap((state)=> {
const obs2$ = ... || ...;
return obs2$.pipe(
map((state2)=>{
返回的状态。a * state2.b
})
)
})
).subscribe((multiplied)=>{
this.setValue(multiplied);
})
// catchError part should be added too..

Also this practice can prevent dangerous code like this.

setArchive(taskId) {
this.getTask(taskId).subscribe(({archive})=>{
if(archive) { // case *1
this.isArchive = true;
} else { // case *2
this.getTaskList().subscribe((list)=>{
this.isArchive = list.length < 1;
});
}
});
}

This code is problematic when it is called several times in a row, because second call with case *1 can be faster than the first call with case *2 and past result would override the value ofisArchive.use switchMap Or concatMap Instead.

| Subscription management

It is important to unsubscribe the steam which does not require any more. If certain observables share lifecycle, good to group it or use takeUntil to unsubscribe the group.

//can be better: many variable names lower readability
const subscription1 = obs1$.subscribe(...);
const subscription2 = obs2$.subscribe(...);
const subscription3 = obs3$.subscribe(...);
// unsubscribe
subscription1.unsubscribe();
subscription2.unsubscribe();
subscription3.unsubscribe();
//is better to use subscription..
const订阅= obs1 .subscribe美元(…);
subscriptions.add(obs2$.subscribe(...));
subscriptions.add(obs3$.subscribe(...));
// unsubscribe
subscriptions.unsubscribe();
//or
const unsbuscribeTrigger$ = new Subject();
obs1$.pipe(takeUntil(unsbuscribeTrigger$)).subscribe(...);
obs2$.pipe(takeUntil(unsbuscribeTrigger$)).subscribe(...);
obs3$.pipe(takeUntil(unsbuscribeTrigger$)).subscribe(...);
// unsubscribe
unsubscribeTrigger$.next();
unsubscribeTrigger$.complete();

Or add SubscriptionManager

export classSubscriptionManager{
staticG_ID= 1;
idx = 0;
subscriptions: Record = {};
instanceId = `SUB_${SubscriptionManager.G_ID++}`;

destroy(): void {
if (!this.subscriptions) return;
this.unsubscribeAll();
this.subscriptions = null;
}

unsubscribeAll(): void {
// to reuse
if (!this.subscriptions) return;
Object.values(this.subscriptions).forEach((sub) => {
if (!sub.closed) {
sub.unsubscribe();
}
});
this.subscriptions = {};
}

unsubscribe(key: string): void {
if (this.subscriptions && this.subscriptions[key]) {
const sub = this.subscriptions[key];
if (!sub.closed) {
sub.unsubscribe();
}
delete this.subscriptions[key];
}
}

add(sub: Subscription, keyData?: string): string {
const key = keyData || `${this.instanceId}_${++this.idx}`;
this.subscriptions[key] = sub;
return key;
}
}

| Hot and Cold Observable

Hot and Cold Observable are important concepts in RxJS, it has something to do with unicast and multicast.

Unicast means the source of data is responsible for one observer, on the other hands, multicast takes multiple of them, which means 2 observers(subscribers) require 2 different data stream.
See the code below and guess how many console.log would be printed.

const createObservable = (type) => {return new Observable((subscriber) => {
let i = 0;
const countdown = () => {
i++;
subscriber.next(i);
setTimeout(() => countdown(), 1000);
};
const start = () => {
console.log(`${type} triggered countdown.`);
countdown();
};
start();
});
};const cold$ = createObservable();cold$.subscribe((n)=>{console.log(n)});
cold$.subscribe((n)=>{console.log(n)});

The answer is 2.cold$has 2 subscriptions, therefore creates 2 observables.

Most time it is not expected result. We created one Observable and returned one, then 2 subscriptions make it double?

To make the observable above multicast we need add 2 operators.

const hot$ = createObservable().pipe(publish(), refCount());cold$.subscribe((n)=>{console.log(n)});
cold$.subscribe((n)=>{console.log(n)});

Now you see 1 console.log
publishmakes your observable multicast(it can be replaced by multicast operator) andrefCountfor managing your subscriptions, when no subscriptions, it will complete the stream. (Another easy option subscribe $cold with Subject— and subscribe the subject.)

Here is the sandbox code for this.

You see, why this is important?
At angular, HttpClient.post is cold observable. It rarely happens, but when you put 2 subscriptions to one httpclient.post()?
Yes, you sent 2 identical post requests!.

So these are some better cases that I fixed with my team. There could be better than this better practice and more better practices with other cases..
I hope it is helpful to you. :)

Noteworthy - The Journal Blog

The Official Journal Blog

Mediumis an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface.Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox.Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic.Write onMedium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store