Angular NgRx: Resolving Route Data

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.

/app.routes.ts

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.

/profile/profile.component.ts

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

/profile/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 as PORFILE_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

/profile/profile.reducer.ts
  • IProfileState — to describe how are we going to extend basic reducer of application.
  • initialState — default value for ProfileData
  • reducer — processes single action (UPDATE) to describe state changes after the action was triggered with some payload

Extending root reducer

/app.state.ts

We compose root state of the application by adding reducer Object to all reducers (LOC: 11 app.state.ts).

/app.module.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.

/app.routes.ts

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.

/profile.resolver.ts

A newly created class implementing 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:

  1. Get data by using getProfileData()
  2. Convert it to Promise (we need only once, so no point for subscribing to Observable)
  3. And dispatch UpdateAction with the data 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.

/profile/profile.component.ts

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.

More by Mykhailo Churilov

Topics of interest

More Related Stories