Throughout this series, we examine ways to preserve declarative patterns in our code as our features evolve through increasing levels of complexity.

Level 5: Working with Asynchronous Data

Suppose our color values originate from a backend API. Angular enables a declarative approach with server data, allowing us to place favoriteColors$ on a service and consume it directly:

  favoriteColors$ = this.colorService.favoriteColors$;
Enter fullscreen mode Exit fullscreen mode

How should we handle this situation now?

Subscribing to that observable directly would require a callback with an imperative property assignment, violating Rule 2.

If this data eventually belongs in the store, the observable should be part of how the store is declared. What about adding another argument to createStore?

export class ColorsComponent {
  // ...
  initialState = ['loading', 'loading', 'loading'];

  favoriteColors$ = this.colorService.fetch('favorite');
  favoriteStore = createStore(
    ['colors.favorite', this.initialState, this.adapter],
    this.favoriteColors$,
  );
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Color Picker—Asynchronous Sources

StackBlitz

Let's say our state takes the form { loading: boolean; colors: string[] }, and we want our observable to feed its emissions into the colors field. If we've already defined a setColors state change in our adapter, it would be convenient to bind that state change directly to the observable, as follows:

  favoriteStore = createStore(
    ['colors.favorite', this.initialState, this.adapter],
    { setColors: this.favoriteColors$ },
  );
Enter fullscreen mode Exit fullscreen mode

Our observable exists independently of any single store, meaning multiple stores could theoretically react to its emissions. Therefore, it deserves its own dedicated annotation for Devtools:

  favoriteColors$ = this.colorService.fetch('favorite').pipe(
    toSource('[Favorite Colors] Received'),
  );
Enter fullscreen mode Exit fullscreen mode

Within Devtools, this should appear as a single log entry labeled [Favorite Colors] Received, with any resulting state changes across all affected stores shown as consequences of that one event.

We want every state change to originate from a source observable annotated in this manner. The one notable exception involves DOM events: since they stem directly from user interactions, they're inherently easy to trace. As mentioned earlier, they already require at least one imperative call in the pipeline, and if that's the only imperative piece, it sufficiently captures the entire essence of the event.

Still, there are scenarios where even DOM events merit independent annotation. That topic comes up in the following article.

And if you're curious about when the HTTP source observable gets subscribed, the answer is straightforward: any subscription to the store's state should propagate down to the store's data sources. A consumer should only need to request what it wants once, via subscription. That's precisely what the term subscribe implies — the consumer wants data and receives it. This is the elegance of RxJS as it was conceived. Dispatching an action or invoking extra logic when we're already requesting store.state$ would introduce an unnecessary imperative step, relying on implicit assumptions about where store.state$ obtains its data. Our store's state could easily be assembled from a sequence of HTTP calls, but RxJS allows us to declare that wiring once, in the appropriate spots. This should appeal strongly to anyone valuing simplicity. And as always, StateAdapt isn't the sole path to this outcome. A piece I wrote in 2017 demonstrates wrapping data dependencies in NgRx using RxJS's using function: Stop Using NgRx/Effects for That. The same approach appears in this article as well: Why and How to Manage State for Angular Reactive Forms. I've also applied this pattern in NGXS projects.