/ rxjs

RxJS Pattern: Asynchronous data sharing with AsyncSubject

Today I want to talk about an RxJS pattern that I use quite often when I need to access asynchronously loaded data from components that don't know when and how the data is retrieved.

A typical scenario would be when you have one part of the page/app that owns the data and is responsible for retrieving it (let's call it Component A) and then you have another part (or many parts) of the page that does something with that data, but it doesn't care where the data comes from, it just needs to get it when available (let's call it Component B).

AsyncSubject-Pattern-2

In this scenario RxJS becomes very handy since it provides the Observable interface that the Component B can use to get the data without knowing where it comes from.

Let's look at a more concrete example using Angular. Suppose we have an app with a main content and a sidebar. The main content component will load a list of items on a certain user action (like a button click) and the sidebar component will display the number of items once the data is available.

Note: I use Angular for this example, but the concepts described in this post are not specific to Angular and are applicable to any app that can use RxJS.

AsyncSubject-Pattern-4

Here is how the code of sidebar.component.ts looks like:

import { Component } from '@angular/core';
import { SharedData } from '../shared-data';

@Component({
  selector: 'app-sidebar',
  template: `
    Item count: <span>{{ getItemCount() | async }}</span>
  `
})
export class SidebarComponent {
  constructor(private sharedData: SharedData) { }

  getItemCount() {
    return this.sharedData.value.map(d => d.items.length);
  }
}

As you can see, SidebarComponent doesn't know where the data comes from, when it is loaded and so on. It only knows that the data comes in a form of Observable<Data>. This is very convenient because we don't need to worry about accessing the data before it is initialized, we can use the RxJS operators to perform any kind of transformations and bind to it directly in the template via the async pipe.

Now let's see how the SharedData class looks like:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AsyncSubject } from 'rxjs/AsyncSubject';

export interface DataValue {
  items: string[];
}

@Injectable()
export class SharedData {
  private readonly valueSubject: AsyncSubject<DataValue> = 
      new AsyncSubject<DataValue>();

  init(value: DataValue) {
    this.valueSubject.next(value);
    this.valueSubject.complete();
  }

  get value(): Observable<DataValue> {
    return this.valueSubject.asObservable();
  }
}

This is where AsyncSubject comes in handy. Here is a quote from the documentation:

The AsyncSubject is a variant [of a Subject] where only the last value of the Observable execution is sent to its observers, and only when the execution completes.

This makes it a good candidate for our scenario where our Observable needs to emit only one value once it is available.

And finally, here is the code of home.component.ts:

import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { SharedData } from '../shared-data';

@Component({
  selector: 'app-main',
  template: `
    <button (click)="loadData()">Load data</button>

    <div *ngIf="sharedData.value | async; let data">
      <h3>Data:</h3>
      <ul>
        <li *ngFor="let item of data.items">{{ item }}</li>
      </ul>
    </div>
  `
})
export class MainComponent {
  constructor(public sharedData: SharedData) { }

  async loadData() {
    let data = await this.loadDataFromHttpService().toPromise();      

    this.sharedData.init(data);
  }

  private loadDataFromHttpService() {
    // Simulate loading data from HTTP service
    return Observable.of({ items: ['a', 'b', 'c']}).delay(2000);
  }
}

Here we load the data from a service and initialize our SharedData object with the result, which makes it instantly available to other parts of the app.

That's it! Live demo and the full source code of the above example are available here: https://stackblitz.com/edit/rx-async-subject-example.

Pavlo Glazkov

Pavlo Glazkov

Programmer. Full stack, with a focus on UI. JavaScript/TypeScript, Angular, Node.js, .NET

Read More