Using Angular NgRx for Reactive Programming

Andres Solorzano
12 min readSep 29, 2022

--

Image from [https://ngrx.io]

Continuing with the series of our FullStack Timer Service app, it’s time to add Reactive Programming to our Ionic/Angular frontend project. Remember that we added Quarkus Reactive support to our Java backend project. It used dependencies like “quarkus-resteasy-reactive-jackson” for our HTTP endpoints and “quarkus-hibernate-orm-panache” for the Quartz relational database. But behind the scenes, Quarkus uses the Mutiny dependency for its reactive purposes. Now, it’s time to do the same thing, but this time for our Angular project using the NgRx library.

To complete this guide, you’ll need the following:

IMPORTANT: The last changes I made in this project are detailed in a new article, “Implementing a Multi-Account Environment on AWS.” So I suggest you go to the new one after reading this article to see the latest project improvements.

NOTE 1: I’ll use my local development environment to validate the app functionality before going to AWS. My previous tutorial shows you more about a local deployment using Docker Compose.

NOTE 2: As usual, you can clone the project repository from my GitHub account to review the final changes in the Tasks Service micro-app.

Deploying the solution

Let’s configure our application from scratch. First, we need to deploy the required AWS services using Amplify. So, we need to configure the authentication module for the Cognito integration:

# amplify init
# amplify add auth
# amplify push

First, go to your Cloud Formation service and copy the “Cognito User Pool ID” generated previously by Amplify. Then, build the Ionic app to verify that it has no errors:

# npm i
# ionic build

If everything is okay, let’s deploy our local environment using Docker Compose.

Then, paste it into the “dev.env” file located in the “utils/docker” directory at the end of the “QUARKUS_OIDC_AUTH_SERVER_URL” property. You can also modify the other properties depending on your requirements:

Deploy your local Docker cluster with the required containers. The following command also builds the docker image of our backend service:

# docker-compose -f utils/docker/docker-compose.yml up --build

Then, in another terminal window, build and deploy the frontend app:

# ionic serve

The app must be opened automatically in your web browser:

Once logged in, you must see the home page of the Timer service app. Create a new Task if you want to verify that all is working as we spect:

Log out and stop the “ionic serve” command execution using CRL+C. In the next section, I will explain all the changes I made to the fronted app to transform it into a new Reactive style.

The Angular Reactive (NgRx) support

Image from [https://ngrx.io/guide/store]

IMPORTANT: This article is not intended to be an entire NgRx tutorial (maybe you might follow an online course); instead, I will share my experience adding all the required NgRx configurations to a functional project like our Timer Service one.

1. Adding NgRx Store

This NgRx library helps us store the application’s state in an In-Memory database style. So, install the NgRx Store library using your terminal window:

# ng add @ngrx/store

NOTE: If you’re facing errors with NPM “eslint” dependencies, try to remove those packages and try again.

After doing that, we need to add some extra configurations to the “app.module.ts” file like this:

2. Adding NgRx Store DevTools

This NgRx library helps us see our NgRx Store while developing the app, but we also need to install a web browser extension. So first install the NgRx Store DevTools library :

# ng add @ngrx/store-devtools

This command adds a configuration line in our “app.module.ts” file, but we need to modify it in a better security way:

Then, go to the Redux DevTools page to install the desired web browser extension based on your browser type:

Finally, try to build and deploy the Ionic app locally to see the changes. If all is good, we must see your In-Memory database in our browser:

We have installed an In-Memory database in a Reactive Programming manner for our Timer Service application.

3. Modifying the Auth module

So, let’s start making the Auth module loaded eagerly. First, update the “auth.module.ts” file by adding the following method:

From the “app.module.ts” file, we need to add the following line to call our “AuthModule” in the eager form:

AuthModule.forRoot(),

Our Timer service app loads the “AuthModule” eagerly and not lazily as before. With this, we gain some performance improvements.

4. Adding “AuthModule” to NgRx Store

It’s time to add our AuthModule to the store. This is a manual process for us because I’ve experienced some NG command issues that I think are for the Ionic project configuration. Luckily for us, we don’t need to make many changes.

First, we must create a “reactive” folder inside the “app/auth” directory. Then, we must create a file called “auth.actions.ts” containing all the actions we need to emit when the user performs some activity in the UI:

Notice that for our Timer Service app, we need to manage the login and logout actions emitted by the user in the UI. For now, we only need to control the login action first.

Then, create the following “auth.reducer.ts” file with the following content:

The login action stores a new State for the Authentication module that contains the user information obtained at login time, as you will see in the “login.component.ts”:

Notice that we need to use the following code to emit the action that, in this case, is when the user is logged in:

this.store.dispatch(AuthActions.loginAction({user}));

Finally, add the following line in the imports section in the “auth.module.ts” file:

StoreModule.forFeature('auth', fromAuth.reducers)

Re-deploy the Ionic app to see the changes in action:

Notice that now, in the “State” tab, we have an “auth: {}” object registered by our NgRx Store.

5. Adding NgRx Effects

We need to execute the following command in our terminal window :

# ng add @ngrx/effects

This command adds the following line in the “auth.module.ts” file:

EffectsModule.forRoot([]),

Then, we must create the following selectors to query the required data inside our AuthState Store. This file is called “auth.selectors” and is located in the “app/auth/reactive” directory:

As shown in the previous image, these selectors query data about our logged users. But we need another code functionality to call those selectors. This file is called “auth.effects.ts” and is located in the “app/auth/reactive” folder:

As you can see, we are addressing the login and logout actions. But you may notice that the “auth.reducer.ts” file is managing these same actions, too. These are like side effects controlled by us when an action is emitted. But notice that in the “auth.effects.ts” file, we store and remove user data from the “localStorage” object.

So, we can conclude that when a login or logout action is emitted, the NgRx calls the “reducer” and “effects” code. The “reducer” manages the NgRx State of the Auth module, and the “effects” collect the user data stored in the localStorage object.

But the question is, why do we need to do that? The answer is that we lose the NgRx Store state when we refresh the browser page of our app. So when this happens, we can call the login action again in our “app.component.ts”:

The previous code stores in the NgRx Store the user data saved in the “localStorage.” This local storage is not removed when the browser refreshes, so we can use it to recreate the user data inside the NgRx Store.

Finally, we must attach this configuration in the “auth.module.ts” file:

EffectsModule.forFeature([AuthEffects])

Notice that the AuthEffects component is declared in the “auth.effects.ts” file.

So now, we must check if these configurations have taken effect. Try to login into the app and open the Redux tool in the browser to see the data stored in action:

Also, remember that the “AuthEffect” component saves the user data in the “localStorage.” So you can open the Application tab in the development tools to see the data stored:

Notice that in the same “localStorage,” Amplify also stores the Cognito User information when we login into the app, so we are not alone in this purpose.

When you log out, the user data is removed from the NgRx Store when the user emits the logout action:

Again, our “AuthEffect” component removes the user data from the “localStorage”:

6. Adding NgRx Router

If you need to store the routes navigated by your users using the app, you need to install the NgRx Router component:

# ng add @ngrx/router-store

Again, this command adds an initial entry in the “app.module.ts,” but we need to update that configuration based on our needs and best practices:

The last line of the configuration indicates to the NgRx Store that the routes navigated by the user must be saved after the execution of any Ng-Guards or NgRx-Resolver. The last one will be used later in the article when we add the NgRx support to our tasks module.

You can verify this configuration in the Redux tab in your browser’s development tools:

Notice that now we have another entry in our app state called “router,” which is the name we configured in the “app.module.ts” file. Also, notice that the initial state in our app contains the “/login” route, which is our initial page when you open the app for the first time.

After you login into the app, the state of the “router” must be the “/task” page:

When you navigate to the task’s creation page, the following must be the state of our “router”:

Finally, notice that the NgRx Store stores all the user navigation pages, so it is executed as expected.

6. Adding NgRx Data

The previous sections showed us the activities needed to configure our “AuthModule” to support NgRx. We can achieve the same actions in our “TasksModule” following the same logic, but the NgRx provides a better way to manage the CRUD operations against our tasks. So first, we need to install the required dependency:

# ng add @ngrx/data

This command adds the following configuration to our “app.module.ts”:

EntityDataModule.forRoot({}),

Also, the command generates an “entity.metadata.ts” file that I moved to the “app/tasks/reactive” folder. I modified the content of this file with the following content:

The first config, “sortComparer,” indicates a method the NgRx store uses to sort the task objects. So, the logic of this method is inside the “tasks.ts” file:

I’m indicating to the NgRx Store to sort the task objects based on the “createdAt” field when the user persists the task data in the backend service.

The “entityDispatcherOptions” that I’m setting is to indicate to the NgRx Store to store task objects after a successful response call to the API service. I’m doing this because the task object can be updated in the backend service with additional data, which I need to save into the NgRx Store at the return of the HTTP response. The other reason is that storing task information in the NgRx Store is not a good idea if the API service call fails. For all these reasons, this approach is called a “pessimistic operation.

Then, in our “tasks.module.ts,” the following configuration needs to be made:

So again, my intention here is to show you the essential changes you need to make in your projects. My purpose is to explain only some things about NgRx. You can follow an online course about NgRx to explore this topic in detail.

As you can see in the previous image, we need to register a Data Service that we can use to perform CRUD operations against our API service. Also, notice that this service must be registered inside an Entity Data Service that manages the task data lifecycle inside the NgRx Store.

The integration between these two services is because the NgRx automatically manages for us the actions, effects, and reducers that we configured previously in our “AuthModule.” So, with one method call, the NgRx automatically calls our API backend service first and then manages the resulting task CRUD operation lifecycle in the NgRx Store (a pessimistic operation).

So, for example, to create a new task in the backend and then in the NgRx Store, we need the following method call obtaining first the data from the form:

To update a task, we need something like this:

And for deleting a task:

With all these configurations, we have almost all CRUD operations for our task objects. Let’s review the final operation of getting data for the backend and storing them in the NgRx Store.

6. Adding NgRx Resolver

A resolver can be seen as an Action Effect dispatched when an angular route is activated. In our case, this happens after the redirection from the login page to the tasks page. This configuration must be added in the routes definition like in our “tasks-routing.module.ts” file. We don’t need to install an additional ng dependency for this purpose.

When we navigate the “/tasks” page, the “TaskResolverService” must be called. This component uses the same Entity Data Service shown in the previous section to obtain the tasks objects from the API service and store them in the NgRx Store automatically:

This component also validates if the task data is loaded in the store before calling the API service. That is because we don’t need to call the API service every time we navigate the “/tasks” route, obtaining performance issues and a poor app user experience.

So finally, let’s perform some CRUD operations to see if all is working as we expect:

And finally, the following is the last state of the NgRx Store after these CRUD operations:

And that’s it!!! Now you have a starting point to start or transform your Angular/Ionic projects into Reactive Programming using the NgRx library.

I hope this tutorial has been of interest to you.

--

--