Ionic 3 & Redux

Every application that we write will need to manage state in some capacity. For example, an application needs to know if a user is logged in or not in order to present the right view (login screen or user dashboard).

We can see here that there is a need for the user’s state to be available for different parts of the application.

In Angular 1, there were a few ways in which state could be dealt with.

  1. Using factory classes to pass data around, this does the job, but it isn’t great.
  2. Storing data locally; this is a really bad idea.
  3. Using $rootScope; the worst thing you could possible do.

As more and more applications were built and grew more complex, managing state got exceedingly difficult and various frameworks popped up. Of all those new frameworks, Redux shined as the winner.

Redux allows us to store our application state in a store (more on this later) which can be accessed by any part of our application. The store is a shared container of our application state, in which components can check for the latest state.

Components can subscribe to the store and listen for changes to a particular piece of state in order to update the component’s local state. This is very important to understand, local state vs application state.

Local State vs Application State

Not all states belongs in a Redux Store, this is something really important to understand.

Let’s take a login form as an example. The LoginComponent’s has a username and password and when these values are added by the user, the LoginComponent’s local state changes.

The WelcomePageComponent doesn’t care about the username and password, that doesn’t relate to it, what the WelcomePageComponent does care about is the user. The user would be stored in the application state.

Okay, so how do we use Redux in Ionic 3?

@ngrx is a Redux inspired library that allows us to take advantage of Redux in our Angular/Ionic projects. The main difference between ngrx and Redux is ngrx is implemented using Observables (RXJS), the heart of Angular. Although ngrx is the angular/ionic flavor of redux, once you have understood ngrx, then you have essentially learned Redux.

Before delving further, we need to build up our Redux Vocabulary. Actions, Reducers, Store, State.

There is a beautiful diagram that I have seen on various articles across the web that I think does a wonderful job of explaining Redux.

 

Ionic 3 Redux Diagram

What is the state?

State is the source of truth when it comes to the data in our application.

What is an action?

Actions are raised by the UI and contain packets of information that are dispatched for processing. An action will have a type and a payload field. The type defines the type of action, and payload, the data associated with this action

To login a user, an action might look something like this

NOTE: The latest version of ngrx has has removed payload in support of strongly typed action types.

What is a reducer?

A reducer is a function that takes the previous state and an action, and returns the next state. Reducer’s should never make an API call or mutate data.

What is the store?

The store is the source of truth when it comes to the state of our application. The store is comprised of different states that include things such as a if a user has logged in, a user’s profile information, recent search results, etc. The store is accessible by any part of the application that is interested in accessing a piece of state.

What is an effect (not in the digram)

Effects can be seen as middleware that are called after an action is dispatched. Effects can do things like making API calls to authenticate the user and dispatch further events (such as success/error).

Enough theory, let’s get our hands dirty.

Let’s create our Ionic 3 application so we can see how we can use redux to login a user and manage the states via redux.

Installing our dependencies

In order for us to leverage Redux, we need three things: Actions, States, and Reducers. Personally, I like to keep related actions, states, and reducers in the same file. Our example will deal with the user logging in and how we can save this state. Let’s define our User model.

Model

Define a user model, taking advantage of typescript’s strong typing: user.ts

For the next few items, we are going to write these in a user.reducer.ts file. Purists may disagree with this approach, however I have found it works well when writing functionality that can be migrated to other applications.

State

We need to define a UserState. Note, because this is a simplified example, we are going to have our UserState contain pieces of information that belong elsewhere (such as whether an http request is loading and error messages).

A well architected app would not do this, but extract those pieces of states to a higher level AppState, however for simplicity we will just put these values in UserState.

Our UserState will have 3 fields:

  1. loading – Tells us whether the http request to log the user in is in flight.
  2. error – Tells us whether or not there was an error logging in the user.
  3. user – Will contain our User object.

Note that we define our state as an interface where we define the fields and field types, and below we set the values of our initial state. Every state should have an initial state.

Now that our project is setup we are going to focus on setting up our central Store. We will define an interface called AppStore. AppStore will contain all of our states. In this case we just have one state.

Actions

Which actions we want to be handled when a user tries to log in? We would like to trigger an action when the user presses the the login button. In that case, there are two other actions to be considered:

  1. The user’s login was successful
  2. The user’s login was not successful

Given these cases, our action types would look like this.

We will also need to define an Action class. We create a LoginAction with a default type, the user that will be returned, and the error message if needed.

Reducer

All actions will flow through the reducer and the reducer will then construct the new state of our application.

What is {…state}

The is an ES6 operator.

This syntax takes our current state variable and creates a new copy and decorates it with new properties.

We cannot do something like this

This is very important to keep in mind. Reducers should never modify the state, that is Redux’s job. A reducer’s job is to just construct the new state based on the new and old information that it has.

Our full user.reducer.ts file

Now we have setup our reducer file, the next thing we need to do is create a UserService that accepts a username and password and logs the user in.

user.service.ts

The UserService is fairly straight forward: we have a login function that takes a username and password and returns the response. But how do we use this UserService so that it can work with our reducer and actions?

This is where @Effect’s come in to play. Like I mentioned before, @Effect’s can be seen as middleware that interact with our actions and reducers.

Let’s write an @Effect that will listen to the login action, and call the login function in our UserService: user.effects.ts

This might look intimidating, so let’s break it down.

A – Use the @Effects decorator to register this effect with ngrx.
B –
Match actions of type UserActionTypes.LOGIN, i.e. this @Effect is run when a dispatcher triggers the Login Action.
C – Call the login function.
D – If successful, dispatch success action with result.
E – If request fails, dispatch failed action.

Here we almost have all of the pieces we need to put everything together. Next, we’ll need to register our effects and reducers with our app, let’s head over to app.module.ts

Now, let’s write our LoginComponent

login.ts

Now, we inject our store and navController into the constructor; the store which contains our userState.

Keep in mind that we also have to hook into two ionic lifecycle events. We subscribe to state changes where we navigate to the home page if the userState contains the user object. We also unsubscribe when we navigate away from this page.

Finally, we dispatch a login event when the user fills out the form. The flow is:

  • User Clicks Login.
  • Dispatch an event to the store.
  • Run through reducer and show the loader.
  • Effect is triggered and the UserService is called.
  • Effect dispatches the Success or Error action.
  • That action flows through our reducer again.
  • Assuming the success action is triggered, the reducers updates our state with the user.
  • The login component detects the user is populated and navigates to the Welcome page.

login.html

Flow example to connect a user:

–| [from component] Dispatch action USER_CONNECT
–| [from user.effect.ts]
—-| Catch action ofType('USER_CONNECT')
—-| Do what you need to do (API call for ex)
—-| When the response comes back :
——| If success: Dispatch USER_CONNECT_SUCCESS
——| If error: Dispatch USER_CONNECT_ERROR

Finally we need to register all of our reducers and effects in our store. We’ll do this in our app.module.ts 

The two most important lines here are:

StoreModule.forRoot({userState: userReducer})
EffectsModule.forRoot([UserEffects])

Remember, we have to add every reducer and effect here.

Now that we have everything wired up, how do we actually use this? We’ll write a login component that will accept a username and password. The first thing we’ll do is inject the NavController and our Store. Remember, our Store contains the state of our application, it is the source of truth when it comes to everything about our application.

You can always check my github for this ionic 3 redux application by following this link:
Github ionic 3 redux

Below you can find a video for this tutorial:

If you enjoyed this article, enter your email below to get free updates!