Managing State in Aurelia: How to Use Aurelia with Redux
This article was peer reviewed by Moritz Kröger and Jedd Ahyoung. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be! Thanks, too, to Simon Codrington for styling the demo.
Nowadays, when developing a web app, a lot of focus is placed on state containers — particularly on all sorts of Flux patterns. One of the most prominent implementations of Flux is Redux . For those of you who haven’t caught the hype train yet, Redux is a library that helps you to keep state mutations predictable. It stores the entire state of your application in a single object tree.
In this article, we’re going to cover the basics of how to use Redux with Aurelia — a next generation open-source JavaScript client framework. But rather than build yet another counter example, we’re going to do something more interesting. We’re going to build a simple markdown editor with undo and redo functionality. The code for this tutorial is available on GitHub and there is a demo of the finished project here.
Note: When learning something new, I prefer to go back to the source and in the case of Redux, there is this awesome Egghead Video series by the Redux creator (Dan Abramov). Since we won’t go into detail on the way Redux works, if you’re in need of a refresher, and have a couple of hours to spare, I can highly recommend giving the series a shot.
How this Tutorial is Structured
In this article, I’m going to build three versions of the same component.
The first version will use a pure Aurelia approach. Here you will learn how to setup an Aurelia app, configure the dependencies and create the necessary View and ViewModel. We will look into building the example the classic Aurelia way using two-way data binding.
The second version will introduce Redux to handle the application state. We will use a vanilla approach, which means no additional plugin to handle the interop. That way you will learn how to use Aurelia’s out of the box features to accommodate a Redux development process.
The final version will implement the undo/redo feature. Anyone who has built this kind of functionality from scratch knows that it is quite easy to get started, but things can quickly get out of hand. That’s why we’ll use the the redux-undo plugin to handle this for us.
Throughout the article you will see several references to the official Aurelia docs, to help you find additional information. All of the code listings also link back to their original source files.
So without any further ado, let’s get started.
Scaffolding a New Aurelia App
Since we’re focusing on the interaction with Aurelia, the example is based on Aurelia’s new preferred way to scaffold an application, the Aurelia CLI.
Following the steps explained in CLI Docs, we install the CLI globally with the following command:
npm install aurelia-cli -g
Next, we’ll create the new app using:
au new aurelia-redux
This will start a dialogue asking whether you would like to use the default setup or customize your choices. Select the default (ESNext) and opt to create the project and install the dependencies. Then change directory into your new project’s folder (using cd aurelia-redux
) and start the development server with:
au run --watch
If everything has gone according to plan, this will fire up a BrowserSync development server instance, listening by default on port 9000. Additionally, it will track changes made to your application and refresh when needed.
Adding Dependencies to the Bundler
The next step is to install the necessary dependencies for our upcoming project. Since the Aurelia CLI builds on top of npm modules we can do this with the following command:
npm install --save marked redux redux-undo
Ok, so let’s go through each of those. Marked is a full-featured, easy to use markdown parser and compiler, which we’re going to use for … well for exactly what it says on the tin. Redux is the package for the library itself and redux-undo is a simple plugin to add undo/redo features for our application’s state container.
Under the hood, the Aurelia CLI uses RequireJS and as such all dependencies are referenced via the Asynchronous Module Definition (AMD) format. Now what’s left is to tell the Aurelia application how and where it can find those dependencies.
In order to do so open the aurelia.json
file found in your app’s aurelia-project
subfolder. If you scroll down to the bundles
section you will see two objects. One for the app-bundle
, containing your own app code, followed by the vendor-bundle
used to bundle all of your app’s dependencies in a separate bundle file. That object contains a property named dependencies
and you guessed it, this is the place where we’re going to add our additional ones.
Manipulating the file
aurelia.json
manually, is currently a necessary step, but one which is going to be automated in future versions.
There are multiple ways to register custom dependencies, best understood by following the respective official Aurelia Docs. What we’re going to add is the following code:
// file: aurelia_project/aurelia.json
...
{
"name": "text",
"path": "../scripts/text"
},
// START OF NEW DEPENDENCIES, DON'T COPY THIS LINE
{
"name": "marked",
"path": "../node_modules/marked",
"main": "marked.min"
},
{
"name": "redux",
"path": "../node_modules/redux/dist",
"main": "redux.min"
},
{
"name": "redux-undo",
"path": "../node_modules/redux-undo/lib",
"main": "index"
},
// END OF NEW DEPENDENCIES, DON'T COPY THIS LINE
{
"name": "aurelia-templating-resources",
"path": "../node_modules/aurelia-templating-resources/dist/amd",
"main": "aurelia-templating-resources"
},
...
Now that everything is set up you should go ahead and restart the CLI watcher to get your newly installed vendor dependencies properly bundled. Remember we do this with the following command:
au run --watch
That’s it, now we’re ready to get our hands dirty with some code.
Adding Some Styling
No markdown editor would be complete without some decent styling. We’ll start off by including a stylish-looking font in index.html
in the root folder.
<head>
<title>Aurelia MarkDown Editor</title>
<link href="https://fonts.googleapis.com/css?family=Passion+One:400,700|Roboto:300,400,500,700"
rel="stylesheet" type="text/css">
</head>
After that we’ll add a bunch of styles to /src/styles.css
. Rather than list all of the CSS here, I’d encourage you to have a look at the CSS file on GitHub and to use these styles in your own project.
Doing it the Aurelia Way
We will start off by creating a new custom element named <markdown-aurelia>
to act as our logical container. We do so by following Aurelia’s default conventions of creating a ViewModel markdown-aurelia.js
and a View markdown-aurelia.html
, inside the src
folder.
Conventions are powerful but sometimes may not fit your application. Note that you always can override them as needed following these instructions
Now let’s look at the View for our new component. Aurelia component Views are enclosed within a <template>
tag, as such all of our markup should be nested inside of that.
We start by requiring our CSS file. Then, after the heading, we’re using a <div>
to house a <textarea>
, which will serve as our editor pane and a second <div>
, which will display the compiled results. These elements have their value
and innerHTML
properties bound to two properties on the ViewModel using Aurelia’s bind command.
For the editor pane we bind to the raw
property on the ViewModel. Aurelia will use two-way binding here by default, as it is a form control.
For the preview <div>
we bind to the innerHTML
property. We do this (instead of a simple ${html}
interpolation) in order for the resulting HTML to be rendered as HTML and not as a string. Aurelia will choose to use a one-way binding in this case, as it didn’t see a contenteditable attribute on the element and thus expects no user input here.
// file: src/markdown-aurelia.html
<template>
<require from="./styles.css"></require>
<h1>Aurelia Markdown Redux</h1>
<div class="markdown-editor">
<textarea class="editor" value.bind="raw"></textarea>
<div class="preview" innerHTML.bind="html"></div>
</div>
</template>
Markdown View, the Aurelia Way
Wow … no Less/Sass/Compass/whatsoever … of course there are many ways to style components in Aurelia. Take a look here to see what options are at your disposal.
There isn’t really more to it so let’s look at the ViewModel, which, to be honest, is just as short. Here, we start off by importing the marked
dependency. Do you remember the wiring process with aurelia.json
we did before? All of that was done to allow this ES6-style import of external modules. Additionally, we import the bindable
decorator.
Following Aurelia’s convention, a ViewModel is a simple ES6 class named using the UpperCamelCased version of the filename. Now we’re going to declare one of the properties on this class (raw
) as bindable using an ES7-style decorator. We need to do this, as we’re using this property to pass information into the component (via the <textarea>
).
After that, we define a html
property to hold the compiled markdown. Finally, we define a rawChanged
function, which will be fired whenever the raw
binding’s value changes. It accepts the newValue
as an argument which can be used as the input for the previously imported marked
function. The return value of this function is assigned to the component’s html
property.
// file: src/markdown-aurelia.js
import marked from 'marked';
import { bindable } from 'aurelia-framework';
export class MarkdownAurelia {
@bindable raw;
html = '';
rawChanged(newValue) {
this.html = marked(newValue);
}
}
Markdown ViewModel, the Aurelia Way
The only thing left to do before we can use our new component is to render it somewhere. We’ll do this inside the app’s root
component, so open the file src/app.html
and replace the content with this:
// file: src/app.html
<template>
<require from="./markdown-aurelia"></require>
<markdown-aurelia raw.bind="data"></markdown-aurelia>
</template>
What we’re doing here is importing the component into the View, by using the <require>
tag. The from
attribute specifies where Aurelia should look for the component.
After that we’re rendering the <markdown-aurelia>
component and binding a data
property to our raw
property, which will act as an initial value for the component.
We define this data
property inside the app.js
file, the corresponding ViewModel to the App
component’s View.
// file: src/app.js
export class App {
constructor() {
this.data = 'Hello World!';
}
}
Setting up the default markdown data
Et voilà! We have a working markdown editor!
Introducing Redux to the Stack
Redux can be described in three key principles. The first principle is the single source of truth. This is all about having one place to store your application state, namely a single JavaScript object (also called the state tree). The second principle is that state is read-only. This guarantees that the state itself cannot be modified, but must be completely replaced. The third principle is that those changes should be made using pure functions. This means no side-effects and that we should always be able to recreate a state in the same way.
There are also three essential entities, used throughout each Redux application: Actions, Reducers and the Store. An action is something you dispatch any time you want to change the state. It is a plain JavaScript object describing the change in the minimum possible terms. Reducers are pure functions that take the state of the app and the action being dispatched and return the next state of the app. Finally, the store holds the state object, it lets you dispatch actions. When you create it you need to pass it a reducer, that specifies how state is to be updated.
That’s as much of a recap as I’d like to give. If you’re in need of a refresher, please consult the official Redux docs, or Dan Abramov’s video course on egghead.io. I can also highly recommend Moritz Kröger’s My Experience With Redux and Vanilla JavaScript here on SitePoint.
Now, without further ado, let’s take a look at the Markdown ViewModel the Redux way.
The Redux way
Let’s start by creating new files markdown-redux.html
and markdown-redux.js
in our src
folder. In both of these files we can simply copy over our existing Aurelia code and in the next steps add the additional Redux parts to them.
Starting with the ViewModel, we first import the createStore
function, which we then use inside of our class declaration, to initialize a store. We pass the store a reference to our reducer function (textUpdater
) and assign it to our class’s store
property. Please note that for simplicity, this example keeps the reducer and action creator in the same file as the ViewModel.
The next change happens inside the constructor where we use the subscribe
function to register an update
callback that the Redux store will call any time an action has been dispatched. You can see that we have leveraged the bind method to pass on the proper execution context to the callback. This callback will take care of rendering all future states.
The update
method itself just requests the latest state from the store using Redux’s getState
method and assigns the resulting values to our html
and raw
properties.
In order to respond to user input, we create a keyupHandler
method which accepts the newValue
as a single argument. Here we come to a crucial part of the Redux philosophy — the only way to trigger a state change is to dispatch an action. As such that is the only thing our handler will do: dispatch a new updateText
action which receives newValue
as an argument.
So far so good? We’re almost there. But since the component will be initialized with some default text — remember the raw property? — we also need to make sure the initial value gets rendered. For this we can leverage Aurelia’s lifecycle hook attached to call the keyupHandler
, once the component has been attached to the DOM.
// file: src/markdown-redux.js
import marked from 'marked';
import { bindable } from 'aurelia-framework';
import { createStore } from 'redux';
export class MarkdownRedux {
@bindable raw;
html = '';
store = createStore(textUpdater);
constructor() {
this.store.subscribe(this.update.bind(this));
}
update() {
const state = this.store.getState();
this.html = state.html;
this.raw = state.raw;
}
keyupHandler(newValue) {
this.store.dispatch(updateText(newValue));
}
attached() {
this.keyupHandler(this.raw);
}
}
Markdown Component the Redux Way – ViewModel
Adding an Action Creator and Reducer
In addition to the ViewModel updates, we also need to take a look at the action and reducer. Remember that Redux is essentially nothing other than a set of functions and, as such, our only action will be created by an updateText
function. This accepts the text
to be converted to HTML, which, in accordance with the Redux philosophy, it encapsulates inside an object with a type
property of TEXT_UPDATE
. The text
property is specified using the ES6 shorthand property name syntax.
Since our example requires one single reducer, textUpdater
acts as the root reducer. The default state, if none is provided, is an object with empty raw
and html
properties, specified using the ES6 default value syntax. The reducer then inspects the action
type and either, as a good practice, returns the state if no match is found, or returns the new state.
// file: src/markdown-redux.js
const TEXT_UPDATE = 'UPDATE';
// action creator
const updateText = (text) => {
return {
type: TEXT_UPDATE,
text
};
};
// reducer
function textUpdater(state = { raw: '', html: '' }, action) {
switch (action.type) {
case TEXT_UPDATE:
return {
raw: action.text,
html: marked(action.text)
};
default:
return state;
}
}
Markdown Component the Redux Way – Action/Reducer
Updating the View
Now if we look at what we’ve achieved with the ViewModel changes, we will notice that updates to the component are limited to either the initializer (the App
component which provides the initial value for the raw
property), or to the update
method. This is in opposition to Aurelia’s two-way binding, which allows you to change the value in a declarative way from within the markup.
Here is how we can modify the View to conform to the new paradigm. Instead of leveraging Aurelia’s bind
keyword we’re going to use one-way
binding for the textarea’s value
attribute. This way we override the default two-way binding behavior and force an unidirectional update process from the ViewModel to the View.
In order to capture user input, we also need to hook up the keyup
event, which we can do with the trigger
binding. Whenever a key is pressed the keyupHandler
should be called and passed the value of the <textarea>
. We use the special $event
property to access the native DOM event and from there the target
‘s value. Last but not least we do not want to rerender on each single keystroke but rather after the user has stopped typing. We can do that by using Aurelia’s debounce binding behavior.
Instead of
trigger
we could have also useddelegate
. Want to understand the difference? Take a look here
// file: src/markdown-redux.html
<template>
<require from="./styles.css"></require>
<h1>Aurelia Markdown Redux</h1>
<div class="markdown-editor cf">
<textarea class="editor"
keyup.trigger="keyupHandler($event.target.value) & debounce"
value.one-way="raw"></textarea>
<div class="preview" innerHTML.bind="html"></div>
</div>
</template>
Markdown Component the Redux Way – View
Finally, do not forget to update app.html
to instantiate the new component
// file: src/app.html
<template>
<require from="./markdown-redux"></require>
<markdown-redux raw.bind="data"></markdown-redux>
</template>
Updating App.html to render Redux-Component
Implementing Undo / Redo
So far we’ve just adapted our original Aurelia Component to leverage the Redux workflow. To be honest, there isn’t much benefit yet. Why did we do all of this? Having a single point where updates happen could have been done with the pure Aurelia approach, as well. It turns out that once again, its all about the functions that make this approach meaningful. In the next step, we’re going to see how we can add undo and redo functionality to our component to handle state changes over time and to navigate back and forth between these.
Let’s start by creating new files markdown.html
and markdown.js
in our src
folder. Again, in both of these files we can simply copy over our existing Aurelia code and in the next steps add the additional code to them.
This time, we’ll do it the other way around and take a look at the View first. Here, we’re adding a new <div>
element above our markdown-editor
section. Inside this element we’re placing two buttons which will act as the undo and redo triggers. We’d also like to render the number of previous states (pastCount
) and future ones (futureCount
) inside the respective buttons. We’ll do this using simple interpolation.
// file: src/markdown.html
<template>
<require from="./styles.css"></require>
<h1>Aurelia Markdown Redux</h1>
<div class="toolbar">
<button click.trigger="undo()">(${pastCount}) Undo</button>
<button click.trigger="redo()">Redo (${futureCount})</button>
</div>
<div class="markdown-editor cf">
...
</div>
</template>
Markdown Component with Undo/Redo – View
Now it’s time to take a look at the changes in the ViewModel. The action creator and reducer stay the same, but what is new is the import of the undoable
function and the ActionCreators
function from the redux-undo module. Notice that the undoable
function is exported by default, so we can do away with the curly braces. We use this function to wrap our textUpdater
reducer function, which we pass to createStore
. This is all that is needed to make our store capable of handling undo and redo functionality.
In addition to this, we’re introducing pastCount
and futureCount
properties, which we initialize to zero. Looking at the update
method we now can see that the default getState
method, instead of returning the state, returns an object with the present
, past
and future
states. We use the present
state to assign the new values to our html
and raw
properties. Since past
and future
are arrays of states we can simply leverage their length
property to update our counts. Last but not least the undo
and redo
methods now dispatch new actions, added automatically by the ActionCreators
object.
// file: src/markdown.js
import marked from 'marked';
import { bindable } from 'aurelia-framework';
import { createStore } from 'redux';
import undoable from 'redux-undo';
import { ActionCreators } from 'redux-undo';
export class Markdown {
@bindable raw;
html = '';
store = createStore(undoable(textUpdater));
pastCount = 0;
futureCount = 0;
constructor() {
...
}
update() {
const state = this.store.getState().present;
this.html = state.html;
this.raw = state.raw;
this.pastCount = this.store.getState().past.length;
this.futureCount = this.store.getState().future.length;
}
keyupHandler(newValue) {
...
}
undo() {
this.store.dispatch(ActionCreators.undo());
}
redo() {
this.store.dispatch(ActionCreators.redo());
}
attached() {
...
}
}
Markdown Component with Undo/Redo – ViewModel
Again update the app.html
to instantiate the final version of the component.
// file: src/app.html
<template>
<require from="./markdown"></require>
<markdown raw.bind="data"></markdown>
</template>
Updating App.html to render Redux-Component
And that is all needed. The reason why this all works so easily, is because we followed the standard workflow that Redux proposes.
Conclusion
The Redux architecture revolves around a strict unidirectional data flow. This has many benefits, but also comes at a cost. If you compare the initial Aurelia way to the first Redux rewrite, you will see that there is a lot more boilerplate involved. Sure there are abstractions and nicer integrations available like the aurelia-redux-plugin (which adds yet another cool approach with dispatcher and selector decorators), but at the end of the day it’s either a question of more code, or more things to learn.
I found that when evaluating new concepts, the most important thing is to really comprehend how they work. Only then are you truly able to decide whether the trade-off in complexity vs stability is right for you. Personally, I do like the idea of thinking of my application as a set of states and I’m more than happy to see an easy out of the box integration (and even deeper ones like the above-mentioned plugin) at your disposal with Aurelia.
I hope you enjoyed this example and have now a better idea how you might be able to apply your existing Redux skills to Aurelia, or borrow ideas and apply them to your default development approach. Let us know about it on the official Aurelia Gitter Channel or in the comments below.