Async pipe versus Subscribe in Angular

Async pipe versus Subscribe in Angular

Over the past year, working in companies using Angular, many times have I been in situations where I was asked to explain the differences between async pipe and .subscribe in Angular.

More precisely explain my standpoint which is to always use async pipe when possible and only use .subscribe when side effect is an absolute necessity.

The challenge in explaining this comes to how to convince without giving an hour boring lesson of why side effects in logic are hard to maintain and how prematured .subscribe forces developers to make unecessary side effects.

So today I would like to talk about that and provide explanations which I hope will help to understand which to use. This post will be composed of three parts:

  1. Observable and Rxjs
  2. Subscribe function
  3. Async pipe
  4. Best practices

1. Observable ans RxJS

First to understand the context, we need to understand what is an observable.

1.1 Observable

Observable is an abstraction of asynchronous stream of data.
For example, when we look at Observable<string>, it represents a stream of strings which will be delivered one by one over the time.

Now why would we care?

We need to care because stream of data coming in an asynchronous fashion is extremely hard to think about. And it is even harder when multiple streams need to be combined. It becomes very error prone to code around it.

To do such operations, we can use RxJS operators.

1.2 RxJS

RxJS operators, which can be found under add/operator, allow us to operate directly on observables, modifying , combining, aggregating, filtering data of observables.

http://reactivex.io/documentation/operators.html

I have said this to many people and this is the most valuable piece of advise I have:

You are safe as long as you stay in the Observable.

We must try to keep the observable as long as possible, combining it or modifying it using RxJS operators. As long as we stay within the observable, we do not need to think about the bigger picture.
All we need to think about is what to do with the single string we receive. We don’t need to care about the fact that we will receive multiple values over the time hence the safety. The power of RxJS is that each operation is assured to receive the output of the previous operation as its own input. This is an extremely powerful model which allows developers to easily follow the code logic making it predictable.

But if we keep the Observable modifying it around, how do we display data? This is where we have been used to .subscribe.

2. Subscribe function

We pass the observable around, combining it, saving it to different variables with different combination of operators but at the end, an Observable<T> is useless on its own. We need a way to “terminate” the observale and extract the type T out of it. That is what .subscribe is used for. To subscribe to the resulting stream and terminate the observable.

Now we could do the following:

expenses: Expense[];

ngOnInit() {
    this.getExpenses()
        .subscribe(expenses => {
            this.expenses = expenses;
        });
}

But as soon as it becomes more complex, like if we need to get a list of expense type to filter, it becomes hard to combine.

expenses: Expense[] = [];
filter = "food";

ngOnInit() {
    this.getExpenses()
        .subscribe(expenses => {
            this.expenses = expenses.filter(e => e.type === this.filter);
        });

    this.getFilter()
        .subscribe(filter => {
            this.filter = filter;
            this.expenses = this.expenses.filter(e => e.type === filter);
        });
}

Now we can already appreciate the benefit of only subscribing when necessary and using the RxJS combinators:


expenses: Expense[] = [];

ngOnInit() {
    this.getExpenses()
        .combineLatest(this.getFilter())
        .subscribe(([expenses, filter]) => {
            this.expenses = expenses.filter(e => e.type === filter);
        });
}

But as I said earlier, we are safe from asynchronousy as long as we stay in the observable therefore we can do even better and never actually use subscribe by using async pipe.

3. Async Pipe

In order to keep the observable, we would transform it as such:

expenses$: Observable<Expense[]>;

ngOnInit() {
    this.expenses$ = this.getExpenses()
        .combineLatest(this.getFilter())
        .map(([expenses, filter]) => expenses.filter(e => e.type === filter));
}

The dollar $ is a convention to know that the variable is an observable. Then to display from the UI, we would need to use the async pipe.

{{ expenses$ | async }}

The other benefit from using the async pipe is that it shows us the way to decompose our UI into components. Because we want to remove the observable, we want to make a binding using [expenses]="{{ expenses$ | async }}" so that the component itself taking as input expenses will be without observable. And this is the true beauty of the framework. By delaying the subscription till the end, we end up forcing ourselves in writing granular components which are abstracted from asynchronousy.

4. Best practices

To summarize, those are the best practices to ensure validity of the logic:

  1. Prefer assignments rather than callbacks, assign Observable rather than subscription,
  2. Let the framework terminate the Observable,
  3. Leverage the power of Angular components and Angular async pipe to code without asynchronousy,
  4. Use libraries like reselect, rxjs to manipulate observable,
  5. Make sure the external variables used inside the rx operators function are const.

Conclusion

Today we saw what was an Observable and how we could use leverage its power using RxJS. We also saw the differences between async pipe and subscription. Lastly I shared my advice which is that we should always use async pipe when possible and only use .subscribe when side effect is an absolute necessity as we are safe as long as we stay in the observable. The code terminating the observable should be the framework (Angular) and the last piece (the UI). Hope you like this post, see you next time!

Comments

  1. Clean VS Safety?
    Good post!!!
    Thanks for sharing!

    ReplyDelete
  2. Very good explanation, thank you !

    ReplyDelete
  3. At least there is some clarity here. Thank you!

    ReplyDelete
  4. Thank you! That's what I call a helpful and well structured article

    ReplyDelete

Post a Comment

Popular posts from this blog

A complete SignalR with ASP Net Core example with WSS, Authentication, Nginx

Microsoft Orleans logs warnings and errors

One way to structure Web App built in F# and WebSharper