Learn the basics of implementing the Redux pattern in Angular applications.
Before we dive into implementing NgRx in an Angular application we should first stop and consider if the Redux pattern is nexessary for our app. Every app is different, so your-mileage-may-vary, and these are generally my opinions and thoughts on implementing the Redux pattern using NgRx in Angular.
Series
This post is part of a series on using NgRX, the Redux-inspired, predictable state container for JavaScript:
Example Project
You can download the source code for the completed NgRx application at:
What is Redux?
Redux is a predictable state container for JavaScript.
Predictable
Redux is predictable because Redux imposes limitations upon how we can change or mutate the state of our application and the domain data that our application uses. We’ll dive more into these limitations shortly. Due to these limitations and the immutabilty of the objects our application uses, the data our application uses is reliable and predictable.
State Container
At the core of Redux is the state container, which we call the store. The store is simply an object that hold’s our application’s state. It’s a tree structure, think directories, that contains all of the entities that our application uses. Some of these entities might include things like: users, orders, and line items. In Redux we only have a single store that contains the state for the entire application.
Pros
There is some debate as to whether the Redux pattern is essential for a Single Page Application. I would say “no”, using Redux is not necessary for every and all projects.
However, using the Redux pattern provides some real benefits:
- Consist behavior.
- Can run your application in multiple environments (client, server, native).
- Easy to test and to debug.
- A great developer experience.
It is also important to note that redux is framework agnostic. It’s a pattern, not a specific implementation that requires the use of a specific framework, such as React or Angular.
Historically speaking, Redux was inspired by the Flux pattern. Flux is an application architecture developed by Facebook for building user interfaces, and is implemented in React.
We’ll be focusing on the NgRx implementation of the Redux pattern in Angular.
Cons
If you are considering using the Redux pattern, and perhaps more specifically, NgRx in your Angular application, you should consider if it is necessary. It might not be necessary because there are some drawbacks:
- Adds complexity.
- Imposes limitations.
- Requires understanding Redux core concepts.
When you begin implementing the Redux pattern using NgRx with Angular, you’re stepping a bit beyond the boundaries of being just an “Angular developer”. This might be challenging for some, but it can also be a lot of fun and a great experience.
Let’s dive into the core concepts of the Redux pattern, and show some examples using NgRx.
Core Concepts
We need to understand a few core concepts before we can start using NgRx:
- Actions
- Reducers
- Effects
Actions
An action is an object that represents an intent to mutate or change the state of our application. An action could be:
- Loading data from a REST API.
- Opening a sidebar or showing a dialog.
- Routing to a different view.
An action is an object that contains at least one property: type
.
The type should be a string, which makes our actions serializable.
Here’s what a simple action object might look like:
{
type: 'LOAD_USER';
}
We might also specify a payload
object to be dispatched along with our action:
{
type: 'LOAD_USER',
payload: { id: 1 }
}
In the example above I am intending to load a user, likely via an HTTP request, and I am specifying the user’s id
as part of the payload
object.
Here’s a better example of an action object that implements the Action
interface provided by NgRx:
export class LoadUser implements Action {
readonly type = 'LOAD_USER';
constructor(public payload: { id: number }) { }
}
We’ll be using the dispatch()
method on the Store
object to notify the store that we want to take a specific action.
Reducers
The reducer in the Redux pattern has the responsibility of acting upon an action to mutate the state of our application.
We only mutate the state of our application within a reducer()
function.
While it might be tempting as a developer to mutate an object that we retreive from the store outside of a reducer function, we’ll prevent any tampering with objects by using the ngrx-store-freeze package. If we attempt to mutate an object we’ll get an exception when we run our application. We should only use the ngrx-store-freeze meta reducer when running our application in a development environment.
A reducer is a function. In fact, a reducer is a pure function. According to wikipedia, a pure function must meet two criteria:
First:
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.
Second:
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices
Here’s an example of a reducer function:
function reducer(state, action) {
switch (action.type) {
case 'LOAD_USER':
return { ...state, loading: true };
case 'LOAD_USER_SUCCESS'
return { ...state, loading: false, user: action.payload }
default:
return state;
}
}
A few things to note about the reducer()
function above:
- First, our
reducer()
function accepts two arguments:state
andaction
. Thestate
object is the current state of the application. Theaction
object is the action that was dispatched by our application that intends to mutate the state of our application. - Next, we are going to
switch
on the action’stype
value. Remember, this is just a string constant that we defined when we declared the action. - Note that our
reducer()
function will mutate the state of the application and return this new state object. We do this by using the spread operator (the leading three dots before the object), which returns a copy of our state object. We then override theloading
and theuser
properties stored in thestate
object. - When the
LOAD_USER
action is dispatched, we are changing theloading
state of our application, setting the value totrue
. - When the
LOAD_USER_SUCCESS
action is dispatched, we are changing theloading
state tofalse
, and we are also storing theuser
, which is thepayload
object for our action. - Finally, note that we return the current
state
object by default. Don’t forget to do this, or you’ll see some odd behavior in your application.
Effects
So far we can define action objects that intend to mutate the state of our application, and we can create reducer()
pure functions that will mutate the state of our application.
But, what about actually performing the HTTP request to load our user.
This is where effects come into play.
An effect listens for actions that are dispatched, and performs some side effect, usuaully an asynchronous request. The benefit to using effects is that it enables us to isolate our async requests outside of our components, making our components less complex. Now, our components just dispatch actions and listen for values to be emitted from the store when our state changes.
Let’s look at an effect that loads a user:
class UsersEffects {
@Effect()
loadUser: Observable<Action> = this.actions.ofType<LoadUser>('LOAD_USER')
.pipe(
map(action => action.payload),
switchMap(payload => this.userService.getUser(payload.id)),
map(user => new LoadUserSuccess(user))
catchError(error => new LoadUserError(error))
)
}
The code above is specific to the NgRx implementation of the Redux pattern.
The example also makes use of the pipe()
method that is implemented on an Observable
when using RxJS 5.5.x, as well as several operators (functions) that are provided by RxJS.
The important things to understand are:
- We have defined a
UsersEffects
class, which has a singleloadUser
property. - We are filtering our actions so that we only perform this particular side effect when the
LOAD_USER
action is dispatched. - We are going to use the
payload
property on our action to invoke thegetUser()
method that we have defined in theUserService
. - We then dispatch a new
LoadUserSuccess
action, specifying the retrieveduser
object as thepayload
for the new action. - Finally, we catch any errors and displatch a new
LoadUserError
action, specifying theerror
object as thepayload
.
In summary, an effect enables us to perform asynchronous side effects that will dispatch new actions as necessary.
RxJs
You may have noticed the pipe()
operator along with several other operators (or functions) in the previous example for creating an effect to load a user.
This is not part of NgRx, rather, these operators are provided by the RxJS library.
According to the RxJS website:
RxJS is a library for reactive programming using
Observables
, to make it easier to compose asynchronous or callback-based code.
RxJS and NgRx play together very well. And, RxJs plays well with Angular. In fact, RxJs is a dependency in Angular. So, if you’re using Angular, then you’re already using RxJs.
If you are unfamiliar with RxJS, I suggest you check out the tutorial provided by RxJS.
Conclusion
In conclusion, the Redux pattern provides several benefits when building Single Page Applications with Angular. And, NgRx is the defacto standard for using the Redux pattern in Angular.
While the Redux pattern, and NgRx in turn, may not be the best solution for your project, it provides several benefits for building Angular applications.
In the next post in this series we’ll implement the Redux pattern using NgRx into an existing project, a Tour of Heroes application.