Angular is an awesome framework.
Redux is an awesome way to manage states.
Middleware is an awesome design pattern.
Now, if we combine them all – wouldn’t that be… well… Really awesome?
Objectives:
We are going to enhance a redux store with middleware. By the end of this post you would know:
- What is a middleware
- How to write a generic function for a Redux middleWare
- How to write a Redux logger and an async fetch middleWare
- Plug it to your store
Check out our repo to see how we did it. Explanations below.
Starting up with angular/cli and angular-redux
First thing first – let’s create our angular project.
If you did not install angular/cli yet – this is the time:
npm i @angular/cli
Now that angular/cli
is installed, let’s setup our project: ng new my-project
Pretty easy, right? Let’s install our dependencies:
- Flux-standard-action – enables us to create custom flux actions for our redux store.
- Redux – Predictable state container for Javascript.
- @angular-redux/store – Redux + Angular bindings.
In order to install these, cmd to your project’s directory and: npm i flux-standard-action redux @angular-redux/store --save
Shoosh – that was tough. Hey wait! Here’s a unicorn! =====>
Let’s create a very simple angular + redux app
You can see the full app in our example-repo (master branch).
Let’s break it down a little bit:
- The app would present a list of items that the user can add / delete / select.
- The app would consist of a list-view component that would hold list-item components and a list-controls for managing buttons.
- Our state would be simple then:
- Items – the list of items in the list
- Selected – the selected item
- Each item’s payload would be a simple string.
As mentioned above, we would be using ngRedux. With ngRedux, you can use configureStore in order to setup our store in angular.
Here’s a link to the original demo, without middleware.
Take note of the store.module.ts file which holds our Items and selected state and uses configureStore in order to create the store. We then use it in our app.module.ts file in order. Now that we have our store setup, let’s move on to create our middleware.
Notice the components we talked about (list-control and list-view). These are regular angular components. You should note how they use redux actions in order to control the list (example).
Do the right thing. Plug a middleware.
Middleware can be a great way to manage your store actions. Middleware are essentially functions that run before your actions. You set up an array of middlware functions that run one after the other. When we are done running all of the middleware, the original store’s dispatch is called.
In our example, we’ll plug a logger middleware, which will print to console the next action to be performed. Not so fancy, I know, but for this demo – it will do. In a real app you’d probably want to use something like log4js for that.
Now, Let’s do A magic trick
For our next show, we’ll create a function you can use for pretty much any Redux middleWare. Our basic middleware function format will be the as follows:
function myMiddleware(store) { return function (next) { return function (action) { // do something // then (usually) - continue to the next action return next(action); }; }; }
Wait. What?
What was that monster? Was it chasing unicorns?
Well, that answer is “no” to both questions, but it is a 3 levels nested functions (monster :)). It looks a bit hairy at first glance, but let’s take a moment to look at each level separately.
The outermost function receives a single parameter – store, which is our, well… store.
The middle one gets a next, which is a pointer to the next middleware or – to the original Redux dispatch function if we reached the end of the middleware array – a.k.a. stack (e.g. if we went through all of our middlewares).
The innermost gets an action. As you can probably guess, it is our action, so you can treat it the same as you would in your reducers with a very important difference – you can call other reducers from within a middleware. We’ll get to it later.
Finally, the innermost function returns an invocation of next(action). Remember, next is a reference to the next middleWare in line or the dispatch function in case it’s the end of the line.
If you’re familiar with node.js, you probably know this pattern from popular libraries like connect and express, which work pretty much the same.
So now you know what is a generic middleWare function, which (almost) any Redux middleWare is built like.
Actually, it might look less intimidating using es6 syntax:
store => next => action => next(action)
Our logger middleware
Now let’s use it to add our logger code:
export function loggerMiddleware(store) { return function (next) { return function (action) { // log a fancy message and action console.log('Hello, this is your captain speaking.', action); // continue to the next action return next(action); }; }; }
Or:
export const loggerMiddleware = store => next => action => { console.log('Hello, this is your captain speaking.', action); next(action); };
All which is left to do is to add our middleWare function as an array at the end of your Redux config:
store.configureStore(rootReducer, INITIAL_STATE, [loggerMiddleware]);
That’s it! Now any invocation of an action will pass through our amazing middleWare. Splendid.
You can see our app with the logger middleWare in our example-repo (middleWare branch)
And We’re done.
What else can you do with a middleware?
The second most common use case for middleware is async operations. Since you can call other actions from within a middleware, you can simply create an async method and then call the right action with the data.
For instance:
export const fetchMiddleware = store => next => action => { if (action.type === 'fetch') { fetch(action.url) .then((response) => { if (response.status !== 200) { console.log('Looks like there was a problem. Status Code: ' + response.status); store.dispatch({ type: 'fetchResponseError', payload: response.status }); } // Examine the text in the response response.json().then((data) => { store.dispatch({ type: 'fetchSuccess', payload: data }); }); } ) .catch(function (err) { console.log('Fetch Error :-S', err); store.dispatch({ type: 'fetchError', payload: err }); }); } return next(action); };
This middleware returns the next(action) as usual, but before that checks if it needs to do a fetch. If it does, it enacts the fetch and on its resolve (or error) enacts a new dispatch that handles success or errors.
This way, you handle async actions, with redux sync actions.
Have a question? a note? a recommendation for pizza?
Leave me a comment below
Hi, great post! Especially the unicorn 🙂
I wanted to ask, what could be the use of an async redux middleware? Could you describe an example situation where an async middleware will be useful?
Thanks Ram!
The unicorn is definitely a significant part of our middleware demo 🙂
As your question, well – you probably want to use async middleware anytime you want your store data changes to be reflected in your back-end server / Database.
For example, let’s go back to our Items App.
I might want to update the server any time the item list has change (e.i. for any delete / add action, but not on change of selected item index). This can be achieved by a middleware which will check was changed / which action was performed, and perform an async action to notify the server about these changes accordingly.
Hope that answer your question.
Yes, nice example. So I understand that we don’t wait until the async action had ended, before updating the redux store. The redux store update proceeds syncronously and immediately, and in parallel we wait for the async action (fetch) that was issued from the middleware. Is that right?
In this case it is so, but it depends on what you’re trying to achieve. For instance, if you want to load data from the server on app initialization, based on your current state, you you might want to invoke async action for that.
This action in turn might invoke another (sync) action, after asynchronous call has ended, to than update the store.