DEV Community

Cover image for Asynchronous Sources (Angular)
Mike Pearson for This is Angular

Posted on • Updated on

Asynchronous Sources (Angular)

This series explores how we can keep our code declarative as we adapt our features to progressively higher levels of complexity.

Level 5: Asynchronous Sources

What if our color state came from a server? Angular lets us be declarative with server data, so we could have favoriteColors$ on a service and access it like this:

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

So what do we do with that now?

If we subscribe to that observable, we need to write a callback function containing an imperative property assignment, which breaks Rule 2.

If that data needs to be part of the store at some point, then the observable should be part of the store's declaration. How about another parameter for 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

What if our state object was shaped like { loading: boolean; colors: string[] } and we wanted our observable to dump its data into the colors property? If we define a setColors state change in our adapter, it would be nice to be able to link up that state change to our observable, like this:

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

Our observable is independent from the store, so theoretically multiple stores could react to it. So it needs its own independent annotation for Devtools:

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

In Devtools that should show up as a single entry in the event log as [Favorite Colors] Received, and state changes from every affected store should be shown as a result of that single event.

Every time a state change happens, we want it to come from a source observable annotated like that. The one possible exception is DOM events, because they arise from user interactions, so they are very easy to keep track of as an exception. They already have to make an imperative call somewhere anyway, as we discussed before, so if it is only a single imperative call, it really does encapsulate the entire meaning of the event.

However, there is a time when DOM events should be annotated independently as well. That's the next article.

Oh, and if you're wondering when our HTTP source observable gets subscribed to, clearly we want any subscription to the store's state to be passed along to the store's own data sources. A consumer should only have to ask for the data it wants once, by subscribing. That's literally the meaning of the word subscribe. It wants the data, it should get it. That's the beauty of RxJS, how it was designed. To dispatch an action or call something extra when it we are already asking for store.state$ would be an unnecessary, imperative step, with implicit knowledge about where store.state$ gets its data from. For all we know, our store's state could come from a long series of HTTP requests, but RxJS lets us declare that concern in the appropriate places only once. This should be extremely desirable to any developer who loves simplicity. And once again, StateAdapt isn't the only way to achieve this. This article I wrote in 2017 explains how to wrap data dependencies in NgRx with RxJS's using function: Stop Using NgRx/Effects for That. I also used the same technique in this article: Why and How to Manage State for Angular Reactive Forms. I've done the same thing in NGXS projects too.

Oldest comments (1)

Collapse
 
marytroxel profile image
MaryTroxel

I've created a simple demo app that shows the different scenarios. Curse to break up a couple