copyright from: blog.ng-book.com
Managing our apps in this way works fine for smaller apps, but as our apps grow, having multiple components manage different parts of the state becomes cumbersome. For instance, passing all of our values down our component tree suffers from the following downsides:
Intermediate property passing – In order to get state to any component we have to pass the values down through
Inflexible refactoring – Because we’re passing
State tree and DOM tree don’t match – The “shape” of our state often doesn’t match the “shape” of our view/component hierarchy. By passing all data through the component tree via
State throughout our app – If we manage state via components, it’s difficult to get a snapshot of the total state of our app. This can make it hard to know which component “owns” a particular bit of data, and which components are concerned about changes
Pulling data out of our components and into services helps a lot. At least if services are the “owners” of our data, we have a better idea of where to put things. But this opens a new question: what are the best practices for “service-owned” data? Are there any patterns we can follow? In fact, there are.
In this post, we’re going to discuss a data-architecture pattern called Redux which was designed to help with these issues. We’ll implement our own version of Redux which will store all of our state in a single place. This idea of holding all of our application’s state in one place might sound a little crazy, but the results are surprisingly delightful.
Data architecture can be a complex topic and so Redux’s best feature is probably its simplicity. If you strip Redux down to the essential core, Redux is fewer than 100 lines of code.
We can build rich, easy to understand, web apps by using Redux as the backbone of our application. But first, let’s walk through how to write a minimal Redux and later we’ll work out patterns that emerge as we work out these ideas in a larger app.
If the above bullet list isn’t clear yet, don’t worry about it – putting these ideas into practice is the goal of the rest of this post.
A reducer must be a pure function. That is:
Reducers should always treat the current state as read-only. A reducer does not change the state instead, it returns a new state. (Often this new state will start with a copy of old state, but let’s not get ahead of ourselves.)
Let’s define our very first reducer. Remember, there are three things involved:
Defining
Since we’re using TypeScript we want to make sure this whole process is typed, so let’s setup an interface for our
The
Our
Notice that our
The
Our
Our
Creating Our First
The simplest possible reducer returns the state itself. (You might call this the identity reducer because it applies the identity function on the state. This is the default case for all reducers, as we will soon see).
Notice that this
We’re not using the
Running Our First
Let’s put it all together and run this reducer:
And run it:
It seems almost silly to have that as a code example, but it teaches us our first principle of reducers:
By default, reducers return the original state.
In this case, we passed a state of the number
But let’s do something more interesting and make our state change.
That said, using a single number for the state lets us focus on other issues for now. So let’s continue with the idea that our
Let’s say we want to be able to change the
Let’s create an
We should also create a second action that instructs our reducer to make the counter smaller with:
Now that we have these actions, let’s try using them in our reducer:
And now we can try out the whole reducer:
Neat! Now the new value of the state is returned according to which action we pass into the reducer.
Reducer
Instead of having so many
Notice that the
But often changes in our app can’t be described by a single value – instead we need parameters to describe the change. This is why we have the
In this counter example, say we wanted to add
Instead, let’s add a
Next, to support this action, we add a new
In the first line we take the state
In Redux, we keep our state in the store. The store has the responsibility of running the reducer and then keeping the new state. Let’s take a look at a minimal store:
Notice that our
We also give our
When we dispatch actions, we’re sending off a notification of what happened. If we want to know what the current state of the system is, we have to check the state of the store.
We start by creating a new
The state is set to
Being Notified with
It’s great that our
Here’s how we want it to work:
After we subscribe a listener, we might want to unsubscribe as well, so lets define the interface for an unsubscribe function as well:
Not much going on here – it’s another function that takes no arguments and has no return value. But by defining these types it makes our code clearer to read.
Our store is going to keep a list of
Now we want to be able to add to that list of
The return value is a function which will update the list of
Trying Out
Now that we can
Above we subscribe to our store and in the callback function we’ll log
Here’s our minimal TypeScript Redux stores in one image (click for larger version):
There are obviously many more things that we need to add to build a large, production web app. However, all of the new ideas that we’ll cover are patterns that flow from building on this simple idea of an immutable, central store of state. If you understand the ideas presented above, you would be likely to invent many of the patterns (and libraries) you find in more advanced Redux apps.
There’s still a lot for us to cover about day-to-day use of redux though. For instance, we need to know:
Messaging App
The
This state object will have a single property,
We can define the type for the app’s state like this:
Messaging App
Our app will process two actions:
The
The
If our messages were objects, we could assign each message an
With that in mind, the
We can define the types for these actions by using the
In this way our
Messaging App
Remember that our reducer needs to handle two actions:
Reducing
We start by switching on the
What would be the problem with the following code?
The problem is that this code mutates the
Here we use the
Here we start with a new store and we call
Next we add three messages to our store. For each message we specify the
Finally we log the new state and we can see that
Our three
Here we’ve created a class with two static methods
This feels much nicer!
An added benefit is that if we eventually decided to change the format of our messages, we could do it without having to update all of our
Next, instead of specifying our initial state when we create the store instead we’re going to let the reducer create the initial state. Here we’ll do this as the default argument to the reducer. This way if there is no state passed in (e.g. the first time it is called at initialization) we will use the initial state:
What’s neat about this is that the rest of our reducer stays the same!
The last thing we need to do is create the store using the
After that, everything else just works!
Now that we have a handle on using Redux in isolation, the next step is to hook it up to our web app. Let’s do that now.
In this section we’re going to create a minimal Angular app that contains just a counter which we can increment and decrement with a button.
By using such a small app we can focus on the integration points between Redux and Angular and then we can move on to a larger app in the next section. But first, let’s see how to build this counter app!
The other thing we need to do when writing Angular apps is decide where we will create components. In this app, we’ll have a top-level
At a high level we’re going to do the following:
Here we are defining our core state structure as
We start by importing the constants
The
The
Since we’re here, let’s look at the action creators
Notice that our action creator functions return the type
However, one of the awesome things about Redux is that it has a robust set of developer tools. Specifically, there is a Chrome extension that will let us monitor the state of our application and dispatch actions.
What’s really neat about the Redux Devtools is that it gives us clear insight to every action that flows through the system and it’s affect on the state.
Not everyone who uses our app will necessarily have the Redux Devtools installed. The code above will check for
Now whenever we dispatch an action and change our state, we can inspect it in our browser!
We’re going to use the
When we want to make something available via DI, then we use the
When we provide something to the DI system, we specify two things:
In the case above, we’re using the class
One problem with us using this method is that we don’t want Angular to create our store – we did it ourselves above with
To do this we’ll use the
The one thing we have left to figure out is what token we want to use to inject. Our
Here we have created a
Now we can use this token
Now we are able to get a reference to our Redux store anywhere in our app by injecting
The
With our setup out of the way, we can start creating our component that actually displays the counter to the user and provides buttons for the user to change the state.
Let’s start by looking at the imports:
We import
Lastly, we import our action creators with
The three things to note here are that we’re:
The
Remember that we need this component depends on the
We use the
We set the
The store will call
The method
We define two helper methods:
Congratulations! You’ve created your first Angular and Redux app!
This post is up to date with angular-4.2.0For many Angular projects we can manage state in a fairly direct way: We tend to grab data from services and render them in components, passing values down the component tree along the way.
tl;dr – In this post we’ll be looking at a data-architecture pattern called Redux. In this post we’re going to discuss:
You can get the completed code here
- the ideas behind Redux,
- build our own mini version of the Redux Store and
- hook it up to Angular 4.
You can try the demo here
Managing our apps in this way works fine for smaller apps, but as our apps grow, having multiple components manage different parts of the state becomes cumbersome. For instance, passing all of our values down our component tree suffers from the following downsides:
Intermediate property passing – In order to get state to any component we have to pass the values down through
inputs
. This means we have many intermediate components passing state that it isn’t directly using or concerned aboutInflexible refactoring – Because we’re passing
inputs
down through the component tree, we’re introducing a coupling between parent and child components that often isn’t necessary. This makes it more difficult to put a child component somewhere else in the hierarchy because we have to change all of the new parents to pass the stateState tree and DOM tree don’t match – The “shape” of our state often doesn’t match the “shape” of our view/component hierarchy. By passing all data through the component tree via
props
we run into difficulties when we need to reference data in a far branch of the treeState throughout our app – If we manage state via components, it’s difficult to get a snapshot of the total state of our app. This can make it hard to know which component “owns” a particular bit of data, and which components are concerned about changes
Pulling data out of our components and into services helps a lot. At least if services are the “owners” of our data, we have a better idea of where to put things. But this opens a new question: what are the best practices for “service-owned” data? Are there any patterns we can follow? In fact, there are.
In this post, we’re going to discuss a data-architecture pattern called Redux which was designed to help with these issues. We’ll implement our own version of Redux which will store all of our state in a single place. This idea of holding all of our application’s state in one place might sound a little crazy, but the results are surprisingly delightful.
Redux
If you haven’t heard of Redux yet you can read a bit about it on the official website. Web application data architecture is evolving and the traditional ways of structuring data aren’t quite adequate for large web apps. Redux has been extremely popular because it’s both powerful and easy to understand.Data architecture can be a complex topic and so Redux’s best feature is probably its simplicity. If you strip Redux down to the essential core, Redux is fewer than 100 lines of code.
We can build rich, easy to understand, web apps by using Redux as the backbone of our application. But first, let’s walk through how to write a minimal Redux and later we’ll work out patterns that emerge as we work out these ideas in a larger app.
There are several attempts to use Redux or create a Redux-inspired system that works with Angular. Two notable examples are:
ngrx
is a Redux-inspired architecture that is heavily observables-based.angular2-redux
uses Redux itself as a dependency, and adds some Angular helpers (dependency-injection, observable wrappers).
Here we’re not going to use either. Instead, we’re going to use Redux directly in order to show the concepts without introducing a new dependency. That said, both of these libraries may be helpful to you when writing your apps.
Redux: Key Ideas
The key ideas of Redux are this:- All of your application’s data is in a single data structure called the state which is held in the store
- Your app reads the state from this store
- This store is never mutated directly
- User interaction (and other code) fires actions which describe what happened
- A new state is created by combining he old state and the action by a function called the reducer.
If the above bullet list isn’t clear yet, don’t worry about it – putting these ideas into practice is the goal of the rest of this post.
Table of Contents
-
Core Redux Ideas
-
Storing Our State
-
A Messaging App
- Using Redux in Angular
- Planning Our App
-
Setting Up Redux
- Providing the Store
- Bootstrapping the App
-
The AppComponent
- What’s Next
- What’s Next
- References
Core Redux Ideas
What’s a reducer?
Let’s talk about the reducer first. Here’s the idea of a reducer: it takes the old state and an action and returns a new state.A reducer must be a pure function. That is:
- It must not mutate the current state directly
- It must not use any data outside of its arguments
Reducers should always treat the current state as read-only. A reducer does not change the state instead, it returns a new state. (Often this new state will start with a copy of old state, but let’s not get ahead of ourselves.)
Let’s define our very first reducer. Remember, there are three things involved:
- An
Action
, which defines what to do (with optional arguments) - The
state
, which stores all of the data in our application - The
Reducer
which takes thestate
and theAction
and returns a new state.
Defining Action
and Reducer
Interfaces
Since we’re using TypeScript we want to make sure this whole process is typed, so let’s setup an interface for our Action
and our Reducer
:
The Action
Interface
Our Action
interface looks like this:Action
has two fields:type
andpayload
type
will be an identifying string that describes the action like INCREMENT
or ADD_USER
. The payload
can be an object of any kind. The ?
on payload?
means that this field is optional.
The Reducer
Interface
Our Reducer
interface looks like this:Reducer
is using a feature of TypeScript called generics. In this case type T
is the type of the state
. Notice that we’re saying that a valid Reducer
has a function which takes a state
(of type T
) and an action
and returns a new state
(also of type T
).
Creating Our First Reducer
The simplest possible reducer returns the state itself. (You might call this the identity reducer because it applies the identity function on the state. This is the default case for all reducers, as we will soon see).Reducer
makes the generic type concrete to number
by the syntax Reducer
. We’ll define more sophisticated states beyond a single number soon.We’re not using the
Action
yet, but let’s try this Reducer
just the same.Running the examples in this post
You can find the code for this post on Github.
In this first section, these examples are run outside of the browser and run by node.js. Because we’re using TypeScript in these examples, you should run them using the commandline toolts-node
, (instead ofnode
directly).
You can installts-node
by running:
Or by doing annpm install
in theangular2-redux-chat
directory and then calling./node_modules/.bin/ts-nodet
For instance, to run the example above you might type (not including the$
):
Use this same procedure for the rest of the code in this post until we instruct you to switch to your browser.
Running Our First Reducer
Let’s put it all together and run this reducer:By default, reducers return the original state.
In this case, we passed a state of the number
0
and a null
action. The result from this reducer is the state 0
.But let’s do something more interesting and make our state change.
Adjusting the Counter With actions
Eventually our state is going to be much more sophisticated than a single number. We’re going to be holding the all of the data for our app in thestate
, so we’ll need better data structure for the state eventually.That said, using a single number for the state lets us focus on other issues for now. So let’s continue with the idea that our
state
is simply a single number that is storing a counter.Let’s say we want to be able to change the
state
number. Remember that in Redux we do not modify the state. Instead, we create actions which instruct the reducer on how to generate a new state.Let’s create an
Action
to change our counter. Remember that the only required property is a type
. We might define our first action like this:
Reducer switch
Instead of having so many if
statements, the common practice is to convert the reducer body to a switch
statement:default
case of the switch
returns the original state
. This ensures that if an unknown action is passed in, there’s no error and we get the original state
unchanged.Q: Wait, all of my application state is in one giantswitch
statement?
A: Yes and no.
If this is your first exposure to Redux reducers it might feel a little weird to have all of your application state changes be the result of a giantswitch
. There are two things you should know:
- Having your state changes centralized in one place can help a ton in maintaining your program, particularly because it’s easy to track down where the changes are happening when they’re all together. (Furthermore, you can easily locate what state changes as the result of any action because you can search your code for the token specified for that action’s
type
)- You can (and often do) break your reducers down into several sub-reducers which each manage a different branch of the state tree. We’ll talk about this later.
Action “Arguments”
In the last example our actions contained only atype
which told our reducer either to increment or decrement the state.But often changes in our app can’t be described by a single value – instead we need parameters to describe the change. This is why we have the
payload
field in our Action
.In this counter example, say we wanted to add
9
to the counter. One way to do this would be to send 9 INCREMENT
actions, but that wouldn’t be very efficient, especially if we wanted to add, say, 9000.Instead, let’s add a
PLUS
action that will use the payload
parameter to send a number which specifies how much we want to add to the counter. Defining this action is easy enough:case
to our reducer that will handle a 'PLUS'
action:PLUS
will add whatever number is in the action.payload
to the state
. We can try it out:3
and PLUS
a payload of 7
, which results in 10
. Neat! However, notice that while we’re passing in a state
, it doesn’t really ever change. That is, we’re not storing the result of our reducer’s changes and reusing it for future actions.Storing Our State
Our reducers are pure functions, and do not change the world around them. The problem is, in our app, things do change. Specifically, our state changes and we need to keep the new state somewhere.In Redux, we keep our state in the store. The store has the responsibility of running the reducer and then keeping the new state. Let’s take a look at a minimal store:
Store
is generically typed – we specify the type of the state with generic type T
. We store the state in the private variable _state
.We also give our
Store
a Reducer
, which is also typed to operate on T
, the state type this is because each store is tied to a specific reducer. We store the Reducer
in the private variable reducer
.In Redux, we generally have 1 store and 1 top-level reducer per application.Let’s take a closer look at each method of our
State
:- In our
constructor
we set the_state
to the initial state. getState()
simply returns the current_state
dispatch
takes an action, sends it to the reducer and then updates the value of_state
with the return value
dispatch
doesn’t return anything. It’s only updating the store’s state (once the result returns). This is an important principle of Redux: dispatching actions is a “fire-and-forget” maneuver. Dispatching actions is not a direct manipulation of the state, and it doesn’t return the new state.When we dispatch actions, we’re sending off a notification of what happened. If we want to know what the current state of the system is, we have to check the state of the store.
Using the Store
Let’s try using our store:Store
and we save this in store
, which we can use to get the current state and dispatch actions.The state is set to
0
initially, and then we INCREMENT
twice and DECREMENT
once and our final state is 1
.
Being Notified with subscribe
It’s great that our Store
keeps track of what changed, but in the above example we have to ask for the state changes with store.getState()
. It would be nice for us to know immediately when a new action was dispatched so that we could respond. To do this we can implement the Observer pattern – that is, we’ll register a callback function that will subscribe to all changes.Here’s how we want it to work:
- We will register a listener function using
subscribe
- When
dispatch
is called, we will iterate over all listeners and call them, which is the notification that the state has changed.
Registering Listeners
Our listener callbacks are a going to be a function that takes no arguments. Let’s define an interface that makes it easy to describe this:Our store is going to keep a list of
ListenerCallbacks
let’s add that to our Store
:_listeners
with a subscribe
function:subscribe
accepts a ListenerCallback
(i.e. a function with no arguments and no return value) and returns an UnsubscribeCallback
(the same signature). Adding the new listener is easy: we push
it on to the _listeners
array.The return value is a function which will update the list of
_listeners
to be the list of _listeners
without the listener
we just added. That is, it returns the UnsubscribeCallback
that we can use to remove this listener from the list.Notifying Our Listeners
Whenever our state changes, we want to call these listener functions. What this means is, whenever wedispatch
a new action, whenever the state changes, we want to call all of the listeners:The Complete Store
We’ll try this out below, but before we do that, here’s the complete code listing for our newStore
:
Trying Out subscribe
Now that we can subscribe
to changes in our store, let’s try it out:subscribed:
and then the current store state.Notice that the listener function is not given the current state as an argument. This might seem like an odd choice, but because there are some nuances to deal with, it’s easier to think of the notification of state changed as separate from the current state. Without digging too much into the weeds, you can read more about this choice here, here, and here.We store the
unsubscribe
callback and then notice that after we call unsubscribe()
our log message isn’t called. We can still dispatch actions, we just won’t see the results until we ask the store for them.If you’re the type of person who likes RxJS and Observables, you might notice that implementing our own subscription listeners could also be implemented using RxJS. You could rewrite ourStore
to use Observables instead of our own subscriptions.
In fact, we’ve already done this for you and you can find the sample code in the filecode/redux/redux-chat/tutorial/06b-rx-store.ts
.
Using RxJS for theStore
is an interesting and powerful pattern if you’re willing to us RxJS for the backbone of our application data.
Here we’re not going to use Observables very heavily, particularly because we want to discuss Redux itself and how to think about data architecture with a single state tree. Redux itself is powerful enough to use in our applications without Observables.
Once you get the concepts of using “straight” Redux, adding in Observables isn’t difficult (if you already understand RxJS, that is). For now, we’re going to use “straight” Redux and we’ll give you some guidance on some Observable-based Redux-wrappers at the end.
The Core of Redux
The above store is the essential core of Redux. Our reducer takes the current state and action and returns a new state, which is held by the store.Here’s our minimal TypeScript Redux stores in one image (click for larger version):
There are obviously many more things that we need to add to build a large, production web app. However, all of the new ideas that we’ll cover are patterns that flow from building on this simple idea of an immutable, central store of state. If you understand the ideas presented above, you would be likely to invent many of the patterns (and libraries) you find in more advanced Redux apps.
There’s still a lot for us to cover about day-to-day use of redux though. For instance, we need to know:
- How to carefully handle more complex data structures in our state
- How to be notified when our state changes without having to poll the state (with subscriptions)
- How to intercept our dispatch for debugging (a.k.a. middleware)
- How to compute derived values (with selectors)
- How to split up large reducers into more manageable, smaller ones (and recombine them)
- How to deal with asynchronous data
While we’ll explain several of these in this post, if you want to go more in-depth with a Redux example with Angular two, checkout the Intermediate Redux chapter in ng-book 4Let’s first deal with handling more complex data structures in our state. To do that, we’re going to need an example that’s more interesting than a counter. Let’s start building a chat app where users can send each other messages.
A Messaging App
In our messaging app, as in all Redux apps, there are three main parts to the data model:- The state
- The actions
- The reducer
Messaging App state
The state
in our counter app was a single number. However in our messaging app, the state
is going to be an object.This state object will have a single property,
messages
. messages
will be an array of strings, with each string representing an individual message in the application. For example:
Messaging App actions
Our app will process two actions: ADD_MESSAGE
and DELETE_MESSAGE
.The
ADD_MESSAGE
action object will always have the property message
, the message to be added to the state. The ADD_MESSAGE
action object has this shape:DELETE_MESSAGE
action object will delete a specified message from the state. A challenge here is that we have to be able to specify which message we want to delete.If our messages were objects, we could assign each message an
id
property when it is created. However, to simplify this example, our messages are just simple strings, so we’ll have to get a handle to the message another way. The easiest way for now is to just use the index of the message in the array (as a proxy for the ID).With that in mind, the
DELETE_MESSAGE
action object has this shape:interface ... extends
syntax in TypeScript:AddMessageAction
is able to specify a message
and the DeleteMessageAction
will specify an index
.
Messaging App reducer
Remember that our reducer needs to handle two actions: ADD_MESSAGE
and DELETE_MESSAGE
. Let’s talk about these individually.
Reducing ADD_MESSAGE
action.type
and handling the ADD_MESSAGE
case.TypeScript objects already have a type, so why are we adding atype
field?
There are many different ways we might choose to handle this sort of “polymorphic dispatch”. Keeping a string in atype
field (wheretype
means “action-type”) is a straightforward, portable way we can use to distinguish different types of actions and handle them in one reducer. In part, it means that you don’t have to create a newinterface
for every action.
That said, it would be more satisfying to be able to use reflection to switch on the concrete type. While this might become possible with more advanced type guards, this isn’t currently possible in today’s TypeScript.
Broadly speaking, types are a compile-time construct and this code is compiled down to JavaScript and we can lose some of the typing metadata.
That said, if switching on atype
field bothers you and you’d like to use language features directly, you could use the decoration reflection metadata. For now, a simpletype
field will suffice.
Adding an Item Without Mutation
When we handle anADD_MESSAGE
action, we need to add the given message to the state. As will all reducer handlers, we need to return a new state. Remember that our reducers must be pure and not mutate the old state.What would be the problem with the following code?
state.messages
array, which changes our old state! Instead what we want to do is create a copy of the state.messages
array and add our new message to the copy.The syntaxRemember that the reducer must return a newwill cast our
action action
to the more specific type. That is, notice that our reducer takes the more general typeAction
, which does not have themessage
field. If we leave off the cast, then the compiler will complain thatAction
does not have a fieldmessage
.
Instead, we know that we have anADD_MESSAGE
action so we cast it to anAddMessageAction
. We use parenthesis to make sure the compiler knows that we want to castaction
and notaction.message
.
AppState
. When we return an object from our reducer it must match the format of the AppState
that was input. In this case we only have to keep the key messages
, but in more complicated states we have more fields to worry about.Deleting an Item Without Mutation
Remember that when we handle theDELETE_MESSAGE
action we are passing the index of the item in the array as the faux ID. (Another common way of handling the same idea would be to pass a real item ID.) Again, because we do not want to mutate the old messages
array, we need to handle this case with care:slice
operator twice. First we take all of the items up until the item we are removing. And we concatenate the items that come after.There are four common non-mutating operations:
The first two (array) operations we just covered. We’ll talk more about the object operations further down, but for now know that a common way to do this is to use
- Adding an item to an array
- Removing an item from an array
- Adding / changing a key in an object
- Removing a key from an object
Object.assign
. As in:
You can think ofObject.assign
as merging objects in from the right into the object on the left.newObject
is merged intooldObject
which is merged into{}
. This way all of the fields inoldObject
will be kept, except for where the field exists innewObject
. NeitheroldObject
nornewObject
will be mutated.
Of course, handling all of this on your own takes great care and it is easy to make a mistake. This is one of the reasons many people use Immutable.js, which is a set of data structures that help enforce immutability.
Trying Out Our Actions
Now let’s try running our actions:store.getState()
and see that we have an empty messages
array.Next we add three messages to our store. For each message we specify the
type
as ADD_MESSAGE
and we cast each object to an AddMessageAction
.Finally we log the new state and we can see that
messages
contains all three messages.Our three
dispatch
statements are a bit ugly for two reasons:- we manually have to specify the
type
string each time. We could use a constant, but it would be nice if we didn’t have to do this and - we’re manually casting to an
AddMessageAction
Action Creators
Instead of creating theADD_MESSAGE
actions directly as objects, let’s create a function to do this for us:addMessage
and deleteMessage
. They return an AddMessageAction
and a DeleteMessageAction
respectively.You definitely don’t have to use static methods for your action creators. You could use plain functions, functions in a namespace, even instance methods on an object, etc. The key idea is to keep them organized in a way that makes them easy to use.Now let’s use our new action creators:
An added benefit is that if we eventually decided to change the format of our messages, we could do it without having to update all of our
dispatch
statements. For instance, say we wanted to add the time each message was created. We could add a created_at
field to addMessage
and now all AddMessageActions
will be given a created_at
field:Using Real Redux
Now that we’ve built our own mini-redux you might be asking, “What do I need to do to use the real Redux?” Thankfully, not very much. Let’s update our code to use the real Redux now!If you haven’t already, you’ll want to runThe first thing we need to do is importnpm install
in thecode/redux/redux-chat/tutorial
directory.
Action
, Reducer
, and Store
from the redux
package. We’re also going to import a helper method createStore
while we’re at it:The last thing we need to do is create the store using the
createStore
helper method from Redux:Using Redux in Angular
In the last section we walked through the core of Redux and showed how to create reducers and use stores to manage our data in isolation. Now it’s time to level-up and integrate Redux with our Angular components.In this section we’re going to create a minimal Angular app that contains just a counter which we can increment and decrement with a button.
By using such a small app we can focus on the integration points between Redux and Angular and then we can move on to a larger app in the next section. But first, let’s see how to build this counter app!
Here we are going to be integrating Redux directly with Angular without any helper libraries in-between. There are several open-source libraries with the goal of making this process easier, and you can find them in the references section below.
That said, it can be much easier to use those libraries once you understand what is going on underneath the hood, which is what we work through here.
Planning Our App
If you recall, the three steps to planning our Redux apps are to:- Define the structure of our central app state
- Define actions that will change that state and
- Define a reducer that takes the old state and an action and returns a new state.
The other thing we need to do when writing Angular apps is decide where we will create components. In this app, we’ll have a top-level
AppComponent
which will have one component, the AppComponent
which contains the view we see in the screenshot.At a high level we’re going to do the following:
- Create our
Store
and make it accessible to our whole app via dependency injection - Subscribe to changes to the
Store
and display them in our components - When something changes (a button is pressed) we will dispatch an action to the
Store
.
Setting Up Redux
Defining the Application State
Let’s take a look at ourAppState
:AppState
– it is an object with one key, counter
which is a number
. In the next example (the chat app) we’ll talk about how to have more sophisticated states, but for now this will be fine.Defining the Reducers
Next lets define the reducer which will handle incrementing and decrementing the counter in the application state:INCREMENT
and DECREMENT
, which are exported by our action creators. They’re just defined as the strings 'INCREMENT'
and 'DECREMENT'
, but it’s nice to get the extra help from the compiler in case we make a typo. We’ll look at those action creators in a minute.The
initialState
is an AppState
which sets the counter to 0
.The
counterReducer
handles two actions: INCREMENT
, which adds 1
to the current counter and DECREMENT
, which subtracts 1
. Both actions use Object.assign
to ensure that we don’t mutate the old state, but instead create a new object that gets returned as the new state.Since we’re here, let’s look at the action creators
Defining Action Creators
Our action creators are functions which return objects that define the action to be taken.increment
and decrement
below return an object that defines the appropriate type
.ActionCreator
. ActionCreator
is a generic class defined by Redux that we use to define functions that create actions. In this case we’re using the concrete class Action
, but we could use a more specific Action
class, such as AddMessageAction
that we defined in the last section.Creating the Store
Now that we have our reducer and state, we could create our store like so:What’s really neat about the Redux Devtools is that it gives us clear insight to every action that flows through the system and it’s affect on the state.
Go ahead and install the Redux Devtools Chrome extension now!In order to use the Devtools we have to do one thing: add it to our store.
window.devToolsExtension
, which is defined by Redux Devtools, and if it exists, we will use it. If it doesn’t exist, we’re just returning an identity function (f => f
) that will return whatever is passed to it.Middleware is a term for a function that enhances the functionality of another library. The Redux Devtools is one of many possible middleware libraries for Redux. Redux supports lots of interesting middleware and it’s easy to write our own.In order to use this
You can read more about Redux middleware here
devtools
we pass it as middleware to our Redux store:Providing the Store
Now that we have the Redux core setup, let’s turn our attention to our Angular components. Let’s create our top-level app component,AppComponent
. This will be the component we use to bootstrap
Angular:We’re going to use the
AppComponent
as the root component. Remember that since this is a Redux app, we need to make our store instance accessible everywhere in our app. How should we do this? We’ll use dependency injection (DI).When we want to make something available via DI, then we use the
providers
configuration to add it to the list of providers
in our NgModule
.When we provide something to the DI system, we specify two things:
- the token to use to refer this injectable dependency
- the way to inject the dependency
useClass
option as in:In the case above, we’re using the class
SpotifyService
as the token in the DI system. The useClass
option tells Angular to create an instance of SpotifyService
and reuse that instance whenever the SpotifyService
injection is requested (e.g. maintain a Singleton).One problem with us using this method is that we don’t want Angular to create our store – we did it ourselves above with
createStore
. We just want to use the store
we’ve already created.To do this we’ll use the
useValue
option of provide
. We’ve done this before with configurable values like API_URL
:The one thing we have left to figure out is what token we want to use to inject. Our
store
is of type Store
:Store
is an interface, not a class and, unfortunately, we can’t use interfaces as a dependency injection key.If you’re interested in why we can’t use an interface as a DI key, it’s because TypeScript interfaces are removed after compilation and not available at runtime.This means we need to create our own token that we’ll use for injecting the store. Thankfully, Angular makes this easy to do. Let’s create this token in it’s own file so that way we can
If you’d like to read more, see here, here, and here.
import
it from anywhere in our application;const AppStore
which uses the OpaqueToken
class from Angular. OpaqueToken
is a better choice than injecting a string directly because it helps us avoid collisions.Now we can use this token
AppStore
with provide
. Let’s do that now.Bootstrapping the App
Back inapp.module.ts
, let’s create the NgModule
we’ll use to bootstrap our app:AppStore
. The place we need it most now is our AppComponent
.Notice that we exported the functionappStoreProviders
fromapp.store.ts
and then used that function inproviders
. Why not use the{ provide: ..., useFactory: ... }
syntax directly? The answer is related to AOT – if we want to ahead-of-time compile a provider that uses a function, we must first export is as a function from another module.
The AppComponent
With our setup out of the way, we can start creating our component that actually displays the counter to the user and provides buttons for the user to change the state.
import
s
Let’s start by looking at the imports:Store
from Redux as well as our injector token AppStore
, which will get us a reference to the singleton instance of our store. We also import the AppState
type, which helps us know the structure of the central state.Lastly, we import our action creators with
* as CounterActions
. This syntax will let us call CounterActions.increment()
to create an INCREMENT
action.The template
Let’s look at the template of ourAppComponent
.In this chapter we are adding some style using the CSS framework Bootstrap
- displaying the value of the counter in
{{ counter }}
- calling the
increment()
function in a button and - calling the
decrement()
function in a button.
The constructor
Remember that we need this component depends on the Store
, so we need to inject it in the constructor. This is how we use our custom AppStore
token to inject a dependency:@Inject
decorator to inject AppStore
– notice that we define the type of the variable store
to Store
. Having a different injection token than the type of the dependency injected is a little different than when we use the class as the injection token (and Angular infers what to inject).We set the
store
to an instance variable (with private store
). Now that we have the store we can listen for changes. Here we call store.subscribe
and call this.readState()
, which we define below.The store will call
subscribe
only when a new action is dispatched, so in this case we need to make sure we manually call readState
at least once to ensure that our component gets the initial data.The method
readState
reads from our store and updates this.counter
to the current value. Because this.counter
is a property on this class and bound in the view, Angular will detect when it changes and re-render this component.We define two helper methods:
increment
and decrement
, each of which dispatch their respective actions to the store.Putting It All Together
Try it out!Congratulations! You’ve created your first Angular and Redux app!
What’s Next
Now that we’ve built a basic app using Redux and Angular, we should try building a more complicated app. When we build bigger apps we encounter new challenges like:- How do we combine reducers?
- How do we extract data from different branches of the state?
- How should we organize our Redux code?
References
If you want to learn more about Redux, here are some good resources:- Official Redux Website
- This Video Tutorial by Redux’s Creator
- Real World Redux (presentation slides)
- The power of higher-order reducers