Managing State in Aurelia: How to Use Aurelia with Redux

Vildan Softic
Share

A monitor with two dinosaurs, a rapidly approaching meteor and undo/redo buttons

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"
},
...

Wiring the App Dependencies

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>

Using the Markdown Component

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 used delegate. 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.