In these Angular NgRX story series I am going to cover specific parts of the Angular application which are not obvious to implement with the NgRx library.
NgRx is RxJS powered state management for Angular applications, inspired by Redux. It helps us to manage a state of the application, and there are some benefits over the standard service-oriented approach.
Short Story
If you are lazy to read a step-by-step explanation, check Github repo example for the full implementation.
Problem
We are writing an Angular app with the NgRX approach. We want to be sure that before we can enter to Profile page we have data to show in this component. You can check the standard implementation of the Route Resolver from official docs. And we will go forward to understand how to achieve that with the NgRX State management.
Pre-conditions
Letās take a look at ProfileComponent
. Itās a classical subscription to Observable been returned by network call from ApiService
. No matter whatās for ProfileComponent
is using this data, letās assume we need to be sure data exists before we enter the component.
Defining Reducer andĀ Actions
My personal preferences to keep files in feature oriented structure. So we will place new files to the same folder as our ProfileComponent
were placed. Here is a new structure of folder after weāve created profile.reducer.ts
and profile.actions.ts
.
Profile folder structure
- profile.component.ts
- profile.component.html
- profile.actions.ts
- profile.reducer.ts
Taking a look at profile.actions.ts
IProfileActions
āāālist of actions which we will use to manipulate data related to Profile entity. In case want to extend functionality, we can add such actions asPORFILE_INIT
,PROFILE_CHANGE
etc. For now, we are fine with action which will trigger for updating of the Profile data.UpdateAction
āāāis single action type which we define to describe an action of getting new data (IProfileData
type).
Now let's handle this action in the profile.reducer.ts
IProfileState
āāāto describe how are we going to extend basic reducer of application.initialState
āāādefault value forProfileData
reducer
āāāprocesses single action (UPDATE) to describe state changes after the action was triggered with some payload
Extending rootĀ reducer
We compose root state of the application by adding reducer Object to all reducers
(LOC: 11 app.state.ts
).
And the last thingāāāwe defining the StoreModule with the reducers weāve created before storeReducers
(LOC: 8 app.module.ts
).
Setting up routeĀ resolver
Now, letās put resolver on the route we want to wait until data will be loaded.
Continue by creating profile.resolver.ts
with the corresponding class as we name it on the LOC: 8 in app.routes.ts
above. This line is saying to wait for navigation to /profile
route until data will be resolved in the ProfileResolver
which we are going to implement in a second.
A newly created class implementing [Resolve](https://angular.io/api/router/Resolve)
interface which obliges us to define _resolve_
function. This function can return Promise, Observable or expected type directly.
Function **waitForProfileDataToLoad**
:
LOC 23: Subscribe store to listen for profile
object changes.
LOC 24: Map values to get the only profileData
from the general profile
object
LOC 25: Ignore all triggers where profileData
does not exist
LOC 26: take(1)āāāgetting only first value, as long as we need single one to resolve route
Function **initProfileData**
:
Getting the first value of profile
object located in the store. If there is no value:
- Get data by using
getProfileData()
- Convert it to Promise (we need only once, so no point for subscribing to Observable)
- And dispatch
UpdateAction
with thedata
which we just got from server
Route resolver conclusion
Weāve initialized data by getting it from the server, and dispatching new state for the application store. Meanwhile, we told our resolve function to listen for the store changes, and return Observable with the first nonempty ProfileData
it will find in the store.
Using route data in component
Finally, we are getting profileData
from route snapshot, assuming it must exist there.
Conclusion
NgRx driven applications make very clear a data flow process through the application. I hope that will give you some patterns how to solve route resolving problem.
Here are few things you can also do to improve your application experience:
**initProfileData**
āāācould be moved or removed if you have some different places to init data.- You can implement spinner to show while route resolvers are in action. There is some basic implementation in the full repo example.
- Your component could be subscribed to store directly if you expect data to be changed from another place. In such case, you can convert
ProfileResolver
to return Boolean instead of data. Could be nice homework exercise to master this topic š.
Github repoĀ example
Thanks for reading! If you want me to write more similar stories recommend this post (by clicking the ā¤ button).
Write your thoughts to comments and subscribe to my medium to find more stories.