In NgRx, loading data from a data source, such as a REST API or a DB, is done using an effect. However, something has to dispatch the action that triggers the effect in the first place. I've seen a few different suggestions / approaches to doing this.
In our example, we'll be loading a collection ofOrder
entities from a service. We'll introduce two actions:LoadOrdersRequested
andLoadOrders
. The first of these will initiate the data load, then an effect will perform the load and dispatch aLoadOrders
action, which will put the loaded data into the store.
The effect to handleLoadOrdersRequested
will look like this:
@Effect()loadOrdersRequested$=this.actions$.pipe(ofType<LoadOrdersRequested>(ActionTypes.LoadOrdersRequested),// Don't load if we've already loaded.withLatestFrom(this.store.select(getAllOrdersLoaded)),filter(([_,loaded])=>!loaded),// Don't handle more than one load request at a time.exhaustMap(()=>this.ordersService.fetchAllOrders().pipe(map(result=>newLoadOrders(result)))));
Now, in order to initiate the data load, we need to dispatch theLoadOrdersRequested
action from somewhere. There are four main options:
- When the app starts.
- When a container component is initialised.
- When the app navigates to a route.
- When the user performs an action.
The fourth of these might be when the user clicks a button to explicitly load or reload some data. For this article we'll concentrate on the first three.
When the app starts
Pros:
- The data is guaranteed to load.
Cons:
- Memory / performance concerns if there's a lot of data to load.
In your AppComponent
The simplest way is to dispatch theLoadOrdersRequested
action from yourAppComponent
's init lifecycle method:
exportclassAppComponentimplementsOnInit{constructor(privatestore:Store<AppState>){}ngOnInit(){this.store.dispatch(newLoadOrdersRequested());}}
https://stackblitz.com/edit/angular-ngrx-initiate-load-at-app-start-app-component
In an effect
NgRx provides anINIT
action that is dispatched when the app starts. This seems like a good place to initiate our data load, but there's a problem. TheINIT
action is dispatched before the effects are subscribed to, so this won't work:
@Effect()init$=this.actions$.pipe(ofType(INIT),map(()=>newLoadOrdersRequested()));
Instead, the NgRx teamhas recommended using thedefer
RxJS operator instead:
@Effect()init$=defer(()=>newLoadOrdersRequested());
However, if we want to have our effect potentially triggerother effects, this approach will not work. This is because, whiledefer
delays the creation of theLoadOrdersRequested
action until theinit$
observable is subscribed to (during the effects module initialisation), the action will then be dispatchedbefore the initialisation is complete. As such, our effect that is looking forLoadOrdersRequested
may not be registered yet, depending on the order in which the effects system has subscribed to the different effects.
We can perhaps mitigate this problem by re-ordering out effects, but a better solution is to use theasyncScheduler
to delay to dispatch of theLoadOrdersRequested
action:
import{asyncScheduler,of}from'rxjs';...@Effect()$init=of(newLoadOrdersRequested,asyncScheduler);
AlthoughINIT
does not work, there is also a built-in action wecan use:ROOT_EFFECTS_INIT
:
@Effect()$init=this.actions$.pipe(ofType(ROOT_EFFECTS_INIT),map(()=>newLoadOrdersRequested()));
https://stackblitz.com/edit/angular-ngrx-initiate-load-at-app-start-init
APP_INITIALIZER
Angular providesAPP_INITIALIZER
as a way to run code at app startup, and you can dispatch actions from there:
@NgModule({...providers:[{provide:APP_INITIALIZER,useFactory:(store:Store<AppState>)=>{return()=>{store.dispatch(newLoadOrdersRequested());};},multi:true,deps:[Store]}]})
https://stackblitz.com/edit/angular-ngrx-initiate-load-at-app-start-app-initializer
When a container component is initialised
Pros:
- You only load data as and when it's needed.
- It's clear from looking at the component what data it's relying on.
Cons:
- You either need lots of actions, or to dispatch the same action in several places.
- The component is less pure, as it has the side effect of loading data.
- You might forget to dispatch the action from a component that needs the data. This bug could be obscured if you normally reach the component through another component thatdoes initiate the data load. E.g. you normally open a list page before opening a details page. Then, one day, you navigate directly to the details page and it breaks.
@Component({...})exportclassOrdersComponentimplementsOnInit{order$:Observable<Order>;constructor(privatestore:Store<AppState>){this.order$=this.store.select(getOrder);}ngOnInit(){this.store.dispatch(newLoadOrdersRequested());}}
https://stackblitz.com/edit/angular-ngrx-initiate-load-in-component
When the app navigates to a route
Pros:
- Less duplication. A single guard at the root of a route hierarchy can load the data for all child routes, even if they're navigated to directly.
- Components are more pure, as they only map from selected state to their template output.
Cons:
- Quite blunt: A guard will trigger data load forany child route, even if its component doesn't need it.
- Less obvious from looking at a component what data it needs to work. If it gets moved somewhere else in the router hierarchy, it'll break.
- Less useful if routes requiring some particular data are spread out throughout the router hierarchy, as you'll need to include the guard in different places.
Router Guard
@Injectable()exportclassOrdersGuardimplementsCanActivate{constructor(privatestore:Store<AppState>){}canActivate():Observable<boolean>{returnthis.store.pipe(select(getAllOrdersLoaded),tap(loaded=>{if(!loaded){this.store.dispatch(newLoadOrdersRequested());}}),filter(loaded=>loaded),first());}}constROUTES:Route[]=[{path:'orders',component:OrdersList,canActivate:[OrdersGuard],children:[...]}]
https://stackblitz.com/edit/angular-ngrx-initiate-load-router-guard
A basic guard could just dispatch theLoadOrdersRequested
action, relying on the effect to filter out unnecessary load requests. However, by checking the condition ofallOrdersLoaded
, the guard can delay the navigation until the load is completed.
Router Resolver
@Injectable()exportclassOrdersResolverimplementsResolve<boolean>{constructor(privatestore:Store<DatasetsState>){}resolve():Observable<boolean>{returnthis.store.pipe(select(allDatasetsLoaded),tap(loaded=>{if(!loaded){this.store.dispatch(newAllDatasetsRequested());}}),filter(loaded=>loaded),first());}}
https://stackblitz.com/edit/angular-ngrx-initiate-load-router-resolve
Using a resolver works very similarly to a guard. The main difference is that a resolve is run under somewhat different circumstances to a guard, and is supposed to return an object to be merged into the activated route's data. However, we should not do this, as components should be retrieving the data from the store,not from the activated route. As such, the resolve should simply return a boolean.
Router Action Effect
@Effect()loadOrders$=this.actions$.pipe(ofType<RouterNavigationAction>(ROUTER_NAVIGATION),withLatestFrom(this.store.select(allOrdersLoaded)),filter(([action,loaded])=>action.payload.routerState.url.includes('/orders')&&!loaded),map(()=>newLoadOrdersRequested()));
Pros:
- Keeps things within NgRx, so feels more idiomatic.
Cons:
- Requires you check if the new route matches, rather than relying on the router itself to do this as the guard approach does. This could cause loading bugs if someone changes a path in your router config, but forgets to do so in your effects.
Dispatch an action from a check inside a selector
exportfunctiongetAllOrders(store:Store<AppState>){returncreateSelector(getOrders,state=>{if(!state.allOrdersLoaded){store.dispatch(newLoadOrdersRequested());}returnstate.orders;});}
I've not actually seen this done in the wild, but it's an approach that occurred to me.
Pros:
- Guarantees to load the data if and only if it's been queried for use.
Cons:
- Violates principle that selectors should be pure functions.
- If you're not rigorous about reusing and combining your selectors, you could end up with some that trigger the load and some that don't because they defer to the selector that triggers the load.
Future possibilities
It sounds as ifAngular Ivy may open up the possibility of using meta-programming on components to configure things like store dependencies in a more declarative way.
Conclusion
I'm not sure any one of these approaches obviously wins over all the others in all situations. You are probably best choosing an approach based on the number of data sources you need to load, the volume of data, and the complexity and layout of your router tree.
E.g. if you have a small, simple app, with a low data volume, eagerly loading everything at INIT is probably the best idea.
However, if you have a large app, split into different features, with each feature needing to load data from a separate source, it might be best to use a guard at the root of each feature's route hierarchy to dispatch the load action for its requirements.
If you have a complex app, with various containers having partially overlapping data requirements, it might be best to have each container dispatch the actions to load what they need.
Top comments(18)

- Email
- LocationBelgium
- Joined
NgRx 7 brings us some new Effect lifecycle hooks.
One of them,OnInitEffects
, might be useful to initiate a data load when an effect gets registered - seedocs.
classOrdersEffectsimplementsOnInitEffects{ngrxOnInitEffects():Action{return{type:'[Orders Effect]: Load'};}}

- LocationItaly
- EducationMathematics
- WorkConsultant at Antreem
- Joined
I was about to say the same thing. Actually, dispatching on a resolver is one of the most common things I do in an Angular+NgRx project.
Nice recap anyway.

Good point about APP_INITIALIZER. I did consider Resolver, but I figured it was similar enough to Guard. I'll update the article with both approaches.

- LocationIsrael
- Education2x M.Sc
- WorkCTO at a stealth mode startup
- Joined
One tiny bit to add, hope this use case is not unique to me. Some data is a cache that does not need to update all that often. Such data is best loaded in the component or router. That is because 1. some of it might have been persisted to localstorage or forage and hydrated into the state 2. some of it might not be stale yet (e.g. my use case I want to invalidate at most once an hour) 3. Critically I want to provide the user the choice to refresh (i.e fetch the latest) - in the same page (smart component) where this info is to be displayed. It means the cache validation will happen onInit, and forceful update on a button click.

I like an idea to dispatch action in selector, because this is the place where you actually know, do you have the data you need. However when I implemented a feature this way, I faced with strange behavior of NGRX\RxJS because in some point of time I receive initial state in the selector instead of updated. Maybe you know how to address this problem?

- LocationGrand Rapids. MI, USA
- Pronounshe/him
- Joined
What a great post! I often struggle with the best approach to load the data. For small apps I like the idea of loading the data in the App Component, but I don't like the idea of loading data that may never be used. For example, if a lazy loaded feature is never interacted with by the user after the data is eagerly loaded.
In my own project I've created a Facade to abstract the NgRx layer from the rest of the Angular code. In there I am dispatching the Load action if the selector does not have the data loaded. This worked best for me, yet I still feel like it violates the NgRx best practices.
At least with this approach I don't need to worry about dispatching a load from my components. Just retrieve() the data and it will pull it from the service or the store.
retrieve(): Observable<CertificationState> { return this.store.select(certificationStateSelector).pipe( tap((state) => { if (state?.data == null) { this.store.dispatch(new CertificationRetrieveAction()); } }) ); }

A bit late to the party. But Jon's post is still at the top of google searches so obviously still quite relevant. We've faced the same issues and also the need to display loading states at a component level. We've elected to go with loading at the component level because it's the most fine grained approach.
The most important consideration is that the loading states should be tied to the ngrx action, not the data it actually loads because different actions could load overlapping pieces of data.
The biggest pain it solves for use is we can issue loading actions without needing to think about whether the data has already been loaded, i.e. a well behaved cache.
It sits strictly on-top ofngrx
, no hidden magic.
A typical component looks like:
ngOnInit():void{this.store.dispatch(Actions.fetchRecord.load({id:this.recordId,maxAge:5000}));this.fetchRecordState$=this.store.select(Selectors.selectFetchRecordState);}
ThemaxAge
tells us it's ok to not load if the last load is less than 5000ms old.@michaelkariv you might be interested in this.
Template can display the loading state as such:
<div*ngIf="(fetchRecordState$ | async).loading">Loading...</div>
ThefetchRecordState
contains a much of other states related to the API call:
{loading:boolean;// Api is loadingsuccess:boolean;// Api returned successfullyissueFetch:boolean;// true if we should issue a fetcherrorHandler:LoadingErrorHandler;successTimestamp?:number;// Millisecond unix timestamp of when data is loaded. Date.now()error?:LrError2;// Api returned error}
Loading actions are bundled to reduce boiler plate:
exportconstfetchRecord=createLoadingActions(`[${NAMESPACE}] Fetch Record`,load<{recordId:string}>(),success<{record:Record}>()// failure action is automatically created.);
Effects handles themaxAge
caching behaviour:
fetchRecord$=createEffect(()=>{returnthis.actions$.pipe(ofType(VaultActions.fetchRecord.load),// This handles the maxAge caching behaviourLoadingStatesManager.filterLoading(this.store.select(fetchRecordSelectors.state)),switchMap((action)=>{returnthis.vaultService.fetchRecord(action.recordId).pipe(map((records)=>{returnVaultActions.fetchRecord.success({record});}),VaultActions.fetchRecord.catchError());}));});
If there is interest, we'll put up an NPM package for it and we can explore togather.

Here's the lib:github.com/amzhang/ngrx-loading-state

- LocationBuenos Aires, Argentina
- EducationUniv. Nac. de La Plata
- WorkSoftware Engineer @ Cloud(X)
- Joined
Thank you Jon, nice post!
What should I do if I have a lot of complex entities (customer > address > country) and I don't want to load all of them in the state at once?
Basically, I need to "fetch" entities on selectors but I don't want to break the pattern.
Anyway, great work!

This is a good question, and it's one I've struggled with myself, and I'm not sure there is a perfect (or even a good) answer for NgRx / Redux as it stands. Usually what I end up doing is having the component(s) interested in a particular entity dispatch a "load" action on ngInit, and then have an effect on this action that checks whether the entity is already present in the store, and then loads it from the back-end if its not. You can mirror this with an "unload" action when the component is destroyed which clears the entity out of the store, so reducing the amount of data you're storing.
However, I have often thought there is room in NgRx for something like a reference-counted selector, where the first subscription to a selector dispatches a "load" event, which can load a resource into the store, and where the subscriber count going back down to zero dispatches an "unload" event. RxJS already has capabilities for performing reference counting on shared observables, so you might be able to use this to create smart selectors in this fashion. I hope to experiment with doing this myself some time, but I haven't yet got around it to it.

- LocationColombia
- EducationTelematics Engineer
- WorkPlatform Software Engineer at Addi
- Joined
Thanks a lot for the post! Just a question: how would you handle loaders and errors when loading data in guards/resolvers?

I was looking for a proper exhaust map implementation of such guard, good work!
but there's a valuable piece missing - the 'loading' state which is set to true at the request dispatch, so on the following dispatches it just leaves the loading state on true
For further actions, you may consider blocking this person and/orreporting abuse