Angular Concepts in React: The Async Pipe
Esteban Hernández

Esteban Hernández @lysofdev

About: Software Engineer specializing on performant web applications.

Location:
Jersey City, NJ
Joined:
Sep 9, 2018

Angular Concepts in React: The Async Pipe

Publish Date: Sep 14 '19
47 7

One of my favorite tools in the Angular framework is the Async pipe. It allows me to render the latest value of an Observable to the screen. The component will then re-render to display the next value of the observable each time it receives a new notification. Finally, the subscription to the observable will be disposed of as soon as the component dismouns. Automatically! It allows me to build my asynchronous code in neat, RxJs streams.

A common use would be to synchronize some value from a data store like NgRx and display it in a component with some adjustments such as formatting. I don't have to use the OnInit life cycle method to start the subscription, nor do I need to use the OnDestroy life cycle method to unsubscribe.

Now, I'm not here to tell you that you should be using RxJs with React. I know I can certainly build a simpler app without it but if I find myself working with many APIs and having tons of Promises to take care of, then I think RxJs is merited. For this reason, I'd like to share with you my personal implementation of the Async pipe from Angular as a React component.

A Simple Example Problem - Stock Prices

We'll build a theoretical, stock monitoring tool which receives a feed of stock prices that we can filter by each stock's ticker. We need a component which displays the ticker and the latest price for this stock. Let's assume that we have a service which provides this value as an Observable stream of number values which we need to reformat as a string with the dollar sign and a two point precision. Simple enough, right?

The Async pipe in Angular 2+

Our Angular component will consume the API from a service, format the latest value and print them to the screen.

import { Component, Input } from '@angular/core';
import { StockPricesService } from './stock-prices.service';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-stock-price',
  templateUrl: 'stock-price.component.html'
})
export class StockPriceComponent {
  @Input() ticker: string;

  price$ = this.stockPrices.getStockPrice(this.ticker).pipe(
    map((price: number) => `$${price.toFixed(2)}`)
  );

  constructor(private stockPrices: StockPricesService) {}
}

Our Component's HTML will look something like this:

<article>
  <section>{{ ticker }}</section>
  <section>{{ price$ | async }}</section>
</article>

Finally, we might use this component like this:

<app-stock-price [ticker]="'GE'"></app-stock-price>

The React version of the Stock Price Component

Here's how we might implement this same component in React:

import React, { useMemo } from "react";
import { map } from "rxjs/operators";

import { Async } from "./async";
import { useStockPrices } from "./use-stock-prices";

export interface StockPriceProps {
  ticker: string;
}

export function StockPrice({ ticker }: StockPriceProps): JSX.Element {
  const { getStockPrice } = useStockPrices();
  const price$ = useMemo(
    () => getStockPrice(ticker).pipe(map((price: number) => `$${price.toFixed(2)}`)),
    [ticker]
  );
  return (
    <article>
      <section>{ticker}</section>
      <section>
        <Async>{price$}</Async>
      </section>
    </article>
  );
}

Obviously, the Async component doesn't exist yet so we'll have to implement it to get the desired effect. Here's how I did it:

import React, { PropsWithChildren, useState, useEffect } from "react";
import { Observable, isObservable, Subscription } from "rxjs";

type AsyncProps<T> = PropsWithChildren<{ observable?: Observable<T> }>;

export function Async<T = any>({
  observable,
  children
}: AsyncProps<T>): JSX.Element {
  const [value, setValue] = useState<T | null>(null);
  useEffect(() => {
    let subscription: Subscription;
    if (isObservable(observable)) {
      subscription = observable.subscribe(setValue);
    } else if (isObservable(children)) {
      subscription = (children as Observable<T>).subscribe(setValue);
    }
    return () => subscription.unsubscribe();
  }, [observable, children]);
  return <>{value}</>;
}

Notice that we can also pass the observable as a prop like so:

<Async observable={price$} />

Is this even useful?

If you're a skeptical developer like I am, you may have noticed that this hardly saves us any trouble from just performing the deliberate subscription on the Stock Price component. We still need to use the useMemo hook to apply the pipe transformation on the observable. It all comes down to how many observable s you want to synchronize your component to. If you only have one, then it's probably not worth it, but if you have more than one or two, or if you're just trying to display the observable directly without transformation, then it may very well be worth it.

What do you think?

Comments 7 total

  • dc
    dcSep 15, 2019

    Why does the return function unsubscribe?

    • Esteban Hernández
      Esteban HernándezSep 17, 2019

      The useEffect hook can optionally return a 'clean-up' function which will be called once the input, in this case the 'ticker' observable changes or if the component unmounts. So, if our Observable where to change then we'd unsubscribe from the first one before subscribing to the new one.

  • Lars Rye Jeppesen
    Lars Rye JeppesenAug 7, 2020

    React is just not geared towards using Observables, it's totally centered around Promises.

    • Esteban Hernández
      Esteban HernándezSep 15, 2020

      Agreed, you can however, call the .toPromise() method on the final Observable.

    • Maksym
      MaksymMar 29, 2025

      Not exactly, using rxjs you can avoid unnecessary renderings passing props as observable$ and don't trigger whole tree while drilling

      and then use something like Observable Hooks for subscribing. Also having model (as classes, just add DI) you will get ultimate framework like Angular but without tons of boilerplate

Add comment