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$;
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$,
);
// ...
}
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$ },
);
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'),
);
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.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.