The original: Don’t follow RxJS Best Practices

Now, more and more developers are learning RxJS and following best practices, but we shouldn’t. These so-called best practices all require learning new things and adding extra code to your project. Furthermore, using best practices increases the risk of creating a good codebase and keeping your colleagues happy!

Here are my suggestions for what to do with the so-called RxJS best practices in Angular:

  • Don’t unsubscribe
  • Subscribe and subscribe and subscribe and subscribe and subscribe and subscribe and subscribe…
  • Don’t use pure functions
  • Always subscribe manually and do not use async
  • Expose subjects in the service
  • Always pass to child components
  • Marble figure? No, this is not for you.

Don’t unsubscribe

Everyone thinks we have to cancel subscriptions to avoid memory leaks. But I disagree. Seriously, who decided you had to unsubscribe? You don’t have to do this. Let’s play a game! Which is the best way to unsubscribe?

The name istakeUntilThe stuff?

@Component({... })export class MyComponent implements OnInit.OnDestroy {

  private destroyed$ = new Subject();

  ngOnInit() {
    myInfiniteStream$
      .pipe(takeUntil(this.destroyed$))
      .subscribe(() = >...). ; }ngOnDestroy() {
    this.destroyed$.next();
    this.destroyed$.complete(); }}Copy the code

Or is it calledtakeWhile?

@Component({... })export class MyComponent implements OnInit.OnDestroy {
  private alive = true;
  ngOnInit() {
    myInfiniteStream$
      .pipe(takeWhile(() = > this.alive))
      .subscribe(() = >...). ; }ngOnDestroy() {
    this.alive = false; }}Copy the code

Honestly, neither of them! Both takeWhile and takeUntil are obscure (🐶 dog head), the best solution is to save each subscription as a separate variable and unsubscribe directly when the component is destroyed:

@Component({... })export class MyComponent implements OnInit.OnDestroy {

  private subscription;

  ngOnInit() {
    this.subscription = myInfiniteStream$
      .subscribe(() = >...). ; }ngOnDestroy() {
    this.subscription.unsubscribe(); }}Copy the code

This works especially well when you have multiple subscriptions:

@Component({... })export class MyComponent implements OnInit.OnDestroy {

  private subscription1;
  private subscription2;
  private subscription3;
  private subscription4;
  private subscription5;

  ngOnInit() {
    this.subscription1 = myInfiniteStream1$
      .subscribe(() = >...). ;this.subscription2 = myInfiniteStream2$
      .subscribe(() = >...). ;this.subscription3 = myInfiniteStream3$
      .subscribe(() = >...). ;this.subscription4 = myInfiniteStream4$
      .subscribe(() = >...). ;this.subscription5 = myInfiniteStream5$
      .subscribe(() = >...). ; }ngOnDestroy() {
    this.subscription1.unsubscribe();
    this.subscription2.unsubscribe();
    this.subscription3.unsubscribe();
    this.subscription4.unsubscribe();
    this.subscription5.unsubscribe(); }}Copy the code

But the solution isn’t perfect. What could be better? How do you think we can make the code clearer and more readable? Yes, the answer is – let’s remove all those ugly unsubscribe statements.

@Component({... })export class MyComponent implements OnInit {

  ngOnInit() {
    myInfiniteStream$
      .subscribe(() = >...). ; }}Copy the code

Brilliant! We removed all the redundant code and now it looks simpler, even saving some memory on our hard drive. But what happens to myInfiniteStream$subscription? Forget it! Let’s leave that to garbage collection, otherwise, why would it exist, right?

Subscribe and subscribe and subscribe and subscribe and subscribe and subscribe and subscribe…

Everyone says we should use the *Map operator instead of subscribing within subscriptions to prevent hell callbacks. But I disagree. Seriously, why not? Why are we using all these switchMap/mergeMap operators? What do you think of this code? Easy to read? Do you really like your colleagues that much?

getUser().pipe(
  switchMap(user= > getDetails(user)),
  switchMap(details= > getPosts(details)),
  switchMap(posts= > getComments(posts)),
)
Copy the code

Don’t you think it’s too neat and cute? You shouldn’t code like that! You have another option, here:

getUser().subscribe(user= > {
  getDetails(user).subscribe(details= > {
    getPosts(details).subscribe(posts= > {
      getComments(posts).subscribe(comments= > {

        // handle all the data here
      });
    });
  });
})
Copy the code

Much better, huh? ! If you hate your teammates and don’t want to learn new RxJS operators, by all means code this way. Be happy! Give your colleagues a little nostalgia for the hell callback.

Don’t use pure functions

Everyone says we should use pure functions to make our code predictable and easier to test. But I disagree. Seriously, why use pure functions? Testability? Composability? It’s hard. Changing the world will be easier. Let’s look at this example:

function calculateTax(tax: number, productPrice: number) {
 return (productPrice * (tax / 100)) + productPrice; 
}
Copy the code

For example, we have a function that computes taxes. It is a pure function that always returns the same result with the same argument, and it is easy to test and combine other functions. But do we really need this kind of behavior? I don’t think so, it would be easier to use a function with no arguments:

window.tax = 20;
window.productPrice = 200;

function calculateTax() {
 return (productPrice * (tax / 100)) + productPrice; 
}
Copy the code

In fact, how could you go wrong? 😉

Always subscribe manually and do not use async

Everyone says we should use async pipes in Angular to simplify subscription management in components. But I disagree. Seriously, we’ve talked about subscription management for takeUntil and takeWhile and agreed that these operators are evil. But why treat Async in a different way?

@Component({  
  template: ` {{ data$ | async }} `,})export class MyComponent implements OnInit {

  data$: Observable<Data>;

  ngOnInit() {
    this.data$ = myInfiniteStream$; }}Copy the code

Did you see that? Clean, readable, and easy to maintain code! Ah, that’s not allowed. For me, it’s better to put the data in a local variable and use that variable in the template.

@Component({  
  template: ` {{ data }} `,})export class MyComponent implements OnInit {
  data;

  ngOnInit() {

    myInfiniteStream$
      .subscribe(data= > this.data = data); }}Copy the code

Expose subjects in the service

There is a common practice in Angular to use Observable Data Services:

@Injectable({ providedIn: 'root' })
export class DataService {

  private data: BehaviorSubject = new BehaviorSubject('bar');

  readonly data$: Observable = this.data.asObservable();

  foo() {
    this.data$.next('foo');
  }

  bar() {
    this.data$.next('bar'); }}Copy the code

Here, we expose the data as an Observable, just to make sure it can only be changed through the data service interface, but it’s confusing. You want to change the data — you have to change the data. If we can change the data in place, why add another method? Let’s rewrite the service to make it easier to use.

@Injectable({ providedIn: 'root' })
export class DataService {
  public data$: BehaviorSubject = new BehaviorSubject('bar');
}
Copy the code

Yeah! Did you see that? Our data services are smaller and easier to read! And now we can put almost anything into our data stream! That’s great, don’t you think?

Always pass to child components

Have you heard of the Smart/Dump component pattern? It helps decouple components from each other. In addition, this pattern prevents child components from triggering operations in parent components:

@Component({
  selector: 'app-parent'.template: ` 
       `,})class ParentComponent implements OnInit {

  data$: Observable<Data>;

  ngOnInit() {
    this.data$ = this.http.get(...);
  }
}

@Component({
  selector: 'app-child',})class ChildComponent {
  @Input() data: Data;
}
Copy the code

Do you like it? Your colleagues will love it, too. But if you want to get back at them, you can rewrite your code as follows:

@Component({
  selector: 'app-parent'.template: ` 
       `,})class ParentComponent {

  data$ = this.http.get(...) ; . }@Component({
  selector: 'app-child',})class ChildComponent implements OnInit {

  @Input() data$: Observable<Data>;

  data: Data;

  ngOnInit(){
    // Trigger data fetch only here
    this.data$.subscribe(data= > this.data = data); }}Copy the code

Did you see that? Instead of processing subscriptions in the parent component, we pass subscriptions directly to the child component. If you follow this exercise, your colleagues will shed blood during debugging, trust me.

Marble figure? No, this is not for you.

Do you know what a pinball chart is? Don’t know? That’s a good thing! Suppose we wrote the following function and wanted to test it:

export function numTwoTimes(obs: Observable<number>) {
  return obs.pipe(map((x: number) = > x * 2))}Copy the code

A lot of people will use pinball charts to test this feature:

it('multiplies each number by 2'.() = > { 
  createScheduler().run(({ cold, expectObservable }) = > {
    const values = { a: 1.b: 2.c: 3.x: 2.y: 4.z: 6 }
    const numbers$ = cold('a-b-c-|', values) as Observable<number>;
    const resultDiagram = 'x-y-z-|';
    expectObservable(numTwoTimes(numbers$)).toBe(resultDiagram, values);
  });
})

Copy the code

But who wants to learn the new concept of pinball? Who wants to write clean code? Let’s rewrite the test in a general way.

it('multiplies each number by 2'.done= > {
  const numbers$ = interval(1000).pipe(
    take(3),
    map(n= > n + 1))// This emits: -1-2-3-|

  const numbersTwoTimes$ = numTwoTimes(numbers$)

  const results: number[] = []

  numbersTwoTimes$.subscribe(
    n= > {
      results.push(n)
    },
    err= > {
      done(err)
    },
    () = > {
      expect(results).toEqual([ 2.4.6 ])
      done()
    }
  )
})
Copy the code

Yeah! It looks a hundred times better now!

conclusion

If you’ve read all the advice above, you’re a hero. But, well, if you recognize your train of thought, I have some bad news for you.

It’s just a joke.

Please, don’t do what I said in that article. Never let your teammates cry and hate you.

Strive to be a decent and tidy person and save the world – using patterns and best practices!

I just decided to cheer you up and make your day better, and I hope you’ll like it.

This is the end of the article, the beginning of the confusion of friends back to stroke again, ha ha!