If you have done any JavaScript development in the past year, then you may have already heard of Redux. Popularized with the use of React, some developers claim it’s the most exciting thing happening in JavaScript at the moment, revolutionizing the way we build our applications and even helping us prevent global warming for good.
Okay, I got a little carried away there. But seriously, Redux does sort of change the way you can build your applications. This post will explain how you can integrate it with Angular 2 alongside another library, Immutable.js.
In this post, we’ll go over the basic concepts of the Flux architecture and Redux. We’ll then go over a simple contact list application step by step, building the basic setup first, adding immutability then finally creating a Redux state container.
As we go along, I’ll do my best to explain why we’re doing each and every step. The final application will not be complicated but will hopefully be enough for you to grasp the main concepts.
Flux is simply an architectural pattern to build user interfaces. It’s not a framework or library, it’s a design pattern for building client side applications.
Source: Flux Documentation - Structure and Data Flow
Redux is an implementation of Flux created by Dan Abramov. Although Flux is not a library on its own, Facebook has created a Dispatcher library in which a Flux-centered application can leverage. Redux follows the same architecture, but aims to make certain abstractions simpler.
Redux preserves all the benefits of Flux (recording and replaying of actions, unidirectional data flow, dependent mutations) and adds new benefits (easy undo-redo, hot reloading) without introducing Dispatcher and store registration.
Let’s look at its basic principles.
1) Everything that changes in your application is contained in a single JavaScript object, known as the store.
2) The store acts as the container for the application state.
3) The state is read-only and it cannot be modified (i.e. it is immutable).
4) To make changes to the state (this can be a user event or an API interaction), you need to send a JavaScript object describing the change. In other words, you need to dispatch an action.
6) Data is transferred from the store to the view and never the other way around. This means that the view cannot alter the state in any way.
As we mentioned, state mutations cannot alter the current state. In Redux, this means that a new JSON object must get returned everytime a mutation occurs. For this to happen, changes to the state need to be triggered through the use of pure functions. A pure function is a function that always returns the same value given the same input. In other words, it cannot modify anything outside of its own scope. This means it can’t alter external variables or make calls to a database.
In Redux, these are known as reducers and are responsible for determining how the application state changes in response to the actions.
Don’t worry if all of this isn’t crystal clear yet. It’ll make more sense as you go through this post and see how reducers, actions and the store are used in a real example.
Let’s start building the contact list example! If you haven’t already, you can set up your application using the Angular 2 QuickStart. I’ll remove the unnecessary class definitions and styling elements in the code embeds and just show you what’s needed. However, I’ll link to the complete source code for each step.
To kick things off, let’s build a simple version with purely Angular elements. We’ll start with a contact store to handle the logic of our application.
This is very similar to how we would set up a service in Angular 1.x. This store now controls the state of the application where we have methods to add, remove and favourite contacts.
Now let’s set up our component.
In here, we have a constructor that defines a private store
property and identifies it as a ContactStore
injection site. The input property methods, addContact
, removeContact
and starContact
, all link to their respective methods in the ContactStore
.
So far we’ve built something simple which works, so that’s a good start. The source code for this can be found here.
Since Angular 2 is component based, it makes more sense to have another component for each of the contacts. So let’s set the parent component to be the actual list.
And the child component will just be that of the contact.
As you can see, the store instance was injected to both the parent and child components. Things are looking a little cleaner now. The source code for this can be found here.
In Angular 2, each and every component has its own change detector responsible for bindings in their own template. For example, we have the {{ contact.name }}
binding for which the Contact
component is responsible for. In other words, the change detection behind the Contact
component projects the data for contact.name
as well as its change.
So what really happens when an event is triggered? In Angular 1.x, when a digest cycle is fired, every binding is triggered in the entire application. Similarly in Angular 2, every single component is also checked. Now wouldn’t it be cool to tell Angular to run change detection on a component only if one of its input properties changed instead of every time an event happens? We can by using Angular’s ChangeDetectionStrategy
in our component level.
It’s as simple as that! Now the change detection for this component will only fire if changes occur to bindings within the component.
Now does this really matter in a simple application like this? Not really, since there aren’t that many bindings in the application in the first place. But for a huge application, this can help significantly reduce the number of bindings to consider when an event is triggered.
To take advantage of this change detection strategy, we need to ensure that the state is indeed immutable. However, the nature of JavaScript objects are by default, mutable. We can solve this by using Immutable.js, a library created by the Facebook team.
Immutable collections should be treated as values rather than objects. While objects represents some thing which could change over time, a value represents the state of that thing at a particular instance of time.
Simply put, immutable objects are essentially objects that cannot change. If we wanted to modify them, we’ll create a new referenced object with that change and keep the original intact. Immutable.js provides a number of immutable data structures that we can include into our applications.
You can install immutable with npm install immutable
. Once that’s complete, you may have to update your SystemJS configuration. If you’ve set up your application following the Angular 2 QuickStart, then this would be in the systemjs.config.js
file. You’ll just need to add another mapped field so the system loader can look for the right file when immutable
is referenced.
And that’s it! Now let’s update the ContactStore
.
As you can see, we changed the instantiation of contacts
and instead of an array, we’re using List
instead. List
is similar to the JavaScript array, but is immutable and completely persistent.
To persist changes to the list, we’re using the push
, delete
and update
methods. Similar to an array, indexOf
is also used to find the selected contact in the list. It’s important to remember that for all these methods, the current collection is not mutated but a new immutable collection is generated.
Here’s the link to the source code for this step.
Note: You may be wondering why <any>
was used in the starContact
method. This is just TypeScript type assertion to prevent compiler errors when returning an updated contact object. This is just a workaround due to an issue with type definitions when running methods against a list (discussed here).
You can install Redux with npm install --save redux
. You’ll also need to update your system configuration once again.
Let’s add our actions file now.
As you can see, all the actions are just simple JavaScript objects. For example:
With Redux, actions must have a type
property which describes the type of the action being executed. It’s also recommended that they be defined as string constants. Other than this, you can set up the structure of an action any way you like. I’ve set it up to include only what’s needed for each action, id
and name
to add a contact and just id
to remove or favourite.
You can see that each of the actions are enclosed in functions that create them.
These are known as action creators, and each one is defined to the IContactAction
interface that we created.
Now let’s see how we can set up a reducer.
Remember what we said earlier about pure functions? You can see that our reducer does not mutate the state or perform any side effects. For each of the actions, it just returns the newly generated state.
Let’s make the necessary updates to our store.
Looks nice and straightforward. The createStore
method creates a Redux store that holds the complete state of the application. For its second argument, the preloaded state of the application, we’re injecting the immutable contacts list. The current state is accessed with getState
and actions are dispatched with the dispatch
method.
We also added an id
field to the Contact
class. Now let’s update our component contact methods.
As you can see, an incrementing ID value is set to each contact that is added. We import the appropriate action from actions
and run the action through dispatch
. Now taking a look at our child component.
And that’s it! That covers up the basics of using Redux in your application. The final source code is here.
As you may have noticed, implementing immutable collections along with a redux state container adds some complexity to your application. This isn’t the only way to build Angular applications, nor is it the best way. It’s one way and it can make things easier under certain circumstances
By default, Angular’s change detection checks every component with any change in the application. Enforcing immutability allows you to use ChangeDetectionStrategy.OnPush
on some of your components so that they get triggered only when they need to (i.e. when their input properties change). A nice and simple way to enforce this is by using a library like Immutable.js.
A better question would be, when should I be implementing a Flux style architecture in my application? I can’t answer it any better than this.
Some great resources to understand how change detection works in Angular 2.
Change Detection in Angular 2 - Victor Savkin
Angular 2 Change Detection Explained - Thoughtram
Dan Abramov has an absolutely excellent course on egghead.io - Getting Started with Redux
Colin Eberhardt goes into some nice detail on integrating Redux with state persistence and time travel - Angular 2 Time Travel with Redux
If you have any questions or suggestions, feel free to open an issue!